diff --git a/config.c b/config.c
index 60f9d23..c363b7a 100644
--- a/config.c
+++ b/config.c
@@ -61,31 +61,26 @@ config_load(IniDispatch *disp, void *config_void)
         }
         if(CONFINI_IS_KEY("core", "database"))
         {
-            config->database = malloc(sizeof(char) * (strlen(disp->value) + 1));
             strcpy(config->database, disp->value);
             return 0;
         }
         if(CONFINI_IS_KEY("core", "not-found-file"))
         {
-            config->not_found_file = malloc(sizeof(char) * (strlen(disp->value) + 1));
             strcpy(config->not_found_file, disp->value);
             return 0;
         }
         if(CONFINI_IS_KEY("core", "not-found-file-type"))
         {
-            config->not_found_file_type = malloc(sizeof(char) * (strlen(disp->value) + 1));
             strcpy(config->not_found_file_type, disp->value);
             return 0;
         }
         if(CONFINI_IS_KEY("core", "not-found-content"))
         {
-            config->not_found_content = malloc(sizeof(char) * (strlen(disp->value) + 1));
             strcpy(config->not_found_content, disp->value);
             return 0;
         }
         if(CONFINI_IS_KEY("core", "not-found-content-type"))
         {
-            config->not_found_content_type = malloc(sizeof(char) * (strlen(disp->value) + 1));
             strcpy(config->not_found_content_type, disp->value);
             return 0;
         }
diff --git a/endpoints/api_v1_tags.c b/endpoints/api_v1_tags.c
new file mode 100644
index 0000000..6ee6226
--- /dev/null
+++ b/endpoints/api_v1_tags.c
@@ -0,0 +1,35 @@
+#include <cJSON.h>
+#include <macros.h>
+#include <constants.h>
+#include <endpoints/api_v1_tags.h>
+#include <logger.h>
+#include <models/tag.h>
+
+void
+api_v1_tags_GET(struct mg_connection *nc, struct http_message *hm, endpoint_args_t *args, endpoint_response_t *response)
+{
+    (void)args;
+    (void)hm;
+    (void)nc;
+
+    char** all_tags = tag_get_all();
+
+    cJSON *json = cJSON_CreateArray();
+
+    for(int i = 0; all_tags[i] != NULL; ++i)
+    {
+        cJSON *json_tag = cJSON_CreateString(all_tags[i]);
+        if (json_tag == NULL)
+        {
+            LOG_DEBUG("failed to add tag from string '%s'\n", all_tags[i]);
+            free(all_tags[i]);
+            continue;
+        }
+        cJSON_AddItemToArray(json, json_tag);
+        free(all_tags[i]);
+    }
+
+    endpoint_response_json(response, 200, json);
+    cJSON_Delete(json);
+    free(all_tags);
+}
diff --git a/include/config.h b/include/config.h
index 17d6db5..1749cbe 100644
--- a/include/config.h
+++ b/include/config.h
@@ -25,15 +25,15 @@ typedef enum
 typedef struct
 {
     char *file;
-    char *database;
+    char database[256];
     log_level_t log_level;
     run_type_t run_type;
     char server_port[6];
     uint16_t discovery_port;
-    char *not_found_file;
-    char *not_found_file_type;
-    char *not_found_content;
-    char *not_found_content_type;
+    char not_found_file[256];
+    char not_found_file_type[256];
+    char not_found_content[256];
+    char not_found_content_type[256];
     struct mg_serve_http_opts http_server_opts;
 } config_t;
 
diff --git a/include/endpoints/api_v1_tags.h b/include/endpoints/api_v1_tags.h
new file mode 100644
index 0000000..fa716c8
--- /dev/null
+++ b/include/endpoints/api_v1_tags.h
@@ -0,0 +1,9 @@
+#ifndef CORE_ENDPOINTS_API_V1_TAGS_H
+#define CORE_ENDPOINTS_API_V1_TAGS_H
+
+#include <router.h>
+
+void
+api_v1_tags_GET(struct mg_connection *nc, struct http_message *hm, endpoint_args_t *args, endpoint_response_t *response);
+
+#endif /* CORE_ENDPOINTS_API_V1_TAGS_H */
diff --git a/include/models/tag.h b/include/models/tag.h
index 79f96fe..fb5580f 100644
--- a/include/models/tag.h
+++ b/include/models/tag.h
@@ -10,8 +10,10 @@ tag_remove(int id);
 char*
 tag_get_tag(int id);
 
+char**
+tag_get_all();
+
 int
 tag_get_id(const char* tag);
 
-
 #endif /* CORE_MODELS_TAG_H */
diff --git a/main.c b/main.c
index f470e0a..303a238 100644
--- a/main.c
+++ b/main.c
@@ -25,8 +25,6 @@ terminate(int signum)
 
     sqlite3_close(global_database);
 
-    free(global_config.database);
-
     router_free();
 
     exit(signum);
@@ -51,9 +49,10 @@ main(int argc, const char** argv)
     /******************** LOAD CONFIG ********************/
 
     global_config.file = "core.ini";
-    global_config.not_found_file = "404.html";
-    global_config.not_found_content = "404 - NOT FOUND";
-    global_config.not_found_content_type = "text/plain";
+    strcpy(global_config.not_found_file, "404.html");
+    strcpy(global_config.not_found_file_type, "text/html");
+    strcpy(global_config.not_found_content, "404 - NOT FOUND");
+    strcpy(global_config.not_found_content_type, "text/plain");
     global_config.log_level = LOG_LEVEL_INFO;
 
     helper_parse_cli(argc, argv, &global_config);
diff --git a/models/tag.c b/models/tag.c
index 1a0802d..fda995e 100644
--- a/models/tag.c
+++ b/models/tag.c
@@ -77,6 +77,50 @@ tag_get_tag(int id)
     return result;
 }
 
+char**
+tag_get_all()
+{
+    sqlite3_stmt *stmt;
+
+    sqlite3_prepare_v2(global_database, "SELECT tag FROM tags;", -1, &stmt, NULL);
+
+    char **all_tags = malloc(sizeof(char*));
+
+    int row = 0;
+
+    while(true)
+    {
+        int s;
+
+        s = sqlite3_step(stmt);
+        if (s == SQLITE_ROW)
+        {
+            const char *new_tag = (const char *)sqlite3_column_text(stmt, 0);
+            int new_tag_len = strlen(new_tag);
+            row++;
+
+            all_tags = (char**)realloc(all_tags, sizeof(char*) * (row + 1));
+            all_tags[row - 1] = malloc(sizeof(char) * (new_tag_len + 1));
+            strcpy(all_tags[row - 1], new_tag);
+        }
+        else
+        {
+            if(s == SQLITE_DONE)
+            {
+                break;
+            }
+            else
+            {
+                LOG_ERROR("error selecting tags from database: %s\n", sqlite3_errstr(s));
+                break;
+            }
+        }
+    }
+    sqlite3_finalize(stmt);
+    all_tags[row] = NULL;
+    return all_tags;
+}
+
 int
 tag_get_id(const char *tag)
 {
diff --git a/router.c b/router.c
index a4b477a..13d7d19 100644
--- a/router.c
+++ b/router.c
@@ -8,6 +8,7 @@
 #include <endpoints/api_v1_schedules.h>
 #include <endpoints/api_v1_controllers.h>
 #include <endpoints/api_v1_relays.h>
+#include <endpoints/api_v1_tags.h>
 
 static endpoint_t endpoints[ROUTER_ENDPOINTS_MAX_COUNT];
 static endpoint_t endpoint_not_found;
@@ -75,6 +76,8 @@ router_init()
 
     router_register_endpoint("/api/v1/relays/", HTTP_METHOD_GET, api_v1_relays_GET);
     router_register_endpoint("/api/v1/relays/tag/{str}", HTTP_METHOD_GET, api_v1_relays_tag_STR_GET);
+
+    router_register_endpoint("/api/v1/tags/", HTTP_METHOD_GET, api_v1_tags_GET);
 }
 
 endpoint_t*
@@ -304,6 +307,7 @@ router_find_endpoint(const char *uri_str, size_t uri_len, struct mg_str *method_
     
     if(best_endpoint == NULL)
     {
+        free(uri);
         return NULL;
     }
 
diff --git a/tests/tavern_tests/3.0.tags.tavern.yaml b/tests/tavern_tests/3.0.tags.tavern.yaml
index 08a28db..d4ef67f 100644
--- a/tests/tavern_tests/3.0.tags.tavern.yaml
+++ b/tests/tavern_tests/3.0.tags.tavern.yaml
@@ -97,3 +97,12 @@ stages:
         number: !int "{returned_number:d}"
         controller_id: "{returned_id}"
         tag: "{returned_tag}"
+
+- name: "[tags] get tags"
+  request:
+    method: GET
+    url: "http://localhost:5000/api/v1/tags/"
+  response:
+    status_code: 200
+    verify_response_with:
+      function: validate_tag:multiple
diff --git a/tests/tavern_utils/validate_tag.py b/tests/tavern_utils/validate_tag.py
new file mode 100644
index 0000000..1e907e5
--- /dev/null
+++ b/tests/tavern_utils/validate_tag.py
@@ -0,0 +1,34 @@
+import json
+
+def _verify_single(tag):
+    assert isinstance(tag, str), "tag is not a string"
+
+def single(response):
+    _verify_single(response.json())
+
+def multiple(response):
+    assert isinstance(response.json(), list), "response is not a list"
+    for tag in response.json():
+        _verify_single(tag)
+
+#def find(response, name=None, number=None, controller_id=None, tag=None):
+#    print(response.json())
+#    for tag in response.json():
+#        if number != None and number != tag.get("number"):
+#            continue
+#
+#        if name != None and name != tag.get("name"):
+#            continue
+#
+#        if controller_id != None and controller_id != tag.get("controller_id"):
+#            continue
+#
+#        if tag != None:
+#            found_in_response = False
+#            for response_tag in tag.get("tags"):
+#                if response_tag == tag:
+#                    found_in_response = True
+#            if not found_in_response:
+#                continue
+#        return
+#    assert False, "tag not found in list"