diff --git a/app/controllers/admin/custom_field_sections_controller.rb b/app/controllers/admin/custom_field_sections_controller.rb
index 3f1e577d..152440f5 100644
--- a/app/controllers/admin/custom_field_sections_controller.rb
+++ b/app/controllers/admin/custom_field_sections_controller.rb
@@ -51,6 +51,7 @@ def custom_field_section_params
         custom_fields_attributes: [
           :id,
           :key,
+          :label,
           :field_type,
           :hint,
           :options,
diff --git a/app/controllers/admin/services_controller.rb b/app/controllers/admin/services_controller.rb
index c220a16e..194089b6 100644
--- a/app/controllers/admin/services_controller.rb
+++ b/app/controllers/admin/services_controller.rb
@@ -192,6 +192,7 @@ def service_params
       ],
       meta_attributes: [
         :id,
+        :label,
         :key,
         :value
       ]
diff --git a/app/controllers/api/v1/custom_fields_controller.rb b/app/controllers/api/v1/custom_fields_controller.rb
new file mode 100644
index 00000000..11c83a0f
--- /dev/null
+++ b/app/controllers/api/v1/custom_fields_controller.rb
@@ -0,0 +1,39 @@
+class API::V1::CustomFieldsController < ApplicationController
+    skip_before_action :authenticate_user!
+  
+    def index
+      render json: json_tree(CustomFieldSection.api_public.includes(:custom_fields)).to_json
+    end
+  
+    private
+  
+    def json_tree(custom_field_sections)
+      custom_field_sections.map do |section|
+        {
+          id: section.id,
+          name: section.name,
+          hint: section.hint,
+          custom_fields: section.custom_fields.map do |field|
+            field_hash = {
+                id: field.id,
+                label: field.label,
+                key: field.key,
+                hint: field.hint,
+                field_type: field.field_type
+              }
+              field_hash[:options] = process_options(field.options) if field.field_type == 'select'
+              field_hash
+          end
+        }
+      end
+    end
+
+    def process_options(options_string)
+        options_string.split(',').map.with_index(1) do |option, index|
+          {
+            value: option.strip,
+            # key: option.strip.parameterize
+          }
+        end
+      end
+  end
\ No newline at end of file
diff --git a/app/controllers/services_controller.rb b/app/controllers/services_controller.rb
index 74c4cfc4..bd71b817 100644
--- a/app/controllers/services_controller.rb
+++ b/app/controllers/services_controller.rb
@@ -158,6 +158,7 @@ def service_params
             meta_attributes: [
             :id,
             :key,
+            :label,
             :value
             ]
         )
diff --git a/app/models/custom_field.rb b/app/models/custom_field.rb
index 6ada0bd0..9190cc05 100644
--- a/app/models/custom_field.rb
+++ b/app/models/custom_field.rb
@@ -1,5 +1,8 @@
 class CustomField < ApplicationRecord
-  validates :key, presence: true, uniqueness: true
+  before_validation :slugify_key
+  
+  validates :key, uniqueness: true, format: { with: /\A[a-z0-9\-]+\z/, message: "must be lowercase, numbers, and dashes only" }
+  validates :label, presence: true, uniqueness: true
   validates_presence_of :field_type
   belongs_to :custom_field_section, counter_cache: :custom_fields_count
 
@@ -12,4 +15,12 @@ def self.types
       "Date"
     ]
   end
+
+  private
+
+  def slugify_key
+    self.key = key.to_s.parameterize if key.present?
+  end
+
+
 end
diff --git a/app/models/custom_field_section.rb b/app/models/custom_field_section.rb
index 00b70f15..ec626193 100644
--- a/app/models/custom_field_section.rb
+++ b/app/models/custom_field_section.rb
@@ -7,4 +7,6 @@ class CustomFieldSection < ApplicationRecord
     default_scope { order(sort_order: :asc) }
 
     scope :visible_to, -> (current_user){ current_user.admin ? all : where(public: true) }
+
+    scope :api_public, -> { where(api_public: true) }
 end
diff --git a/app/models/service_meta.rb b/app/models/service_meta.rb
index ec884597..96175f36 100644
--- a/app/models/service_meta.rb
+++ b/app/models/service_meta.rb
@@ -1,5 +1,5 @@
 class ServiceMeta < ApplicationRecord
   belongs_to :service
-  validates :key, presence: true
-  validates_uniqueness_of :key, scope: :service_id
+  validates :label, presence: true
+  validates_uniqueness_of :label, scope: :service_id
 end
diff --git a/app/serializers/service_meta_serializer.rb b/app/serializers/service_meta_serializer.rb
index c6f03512..b70b20a0 100644
--- a/app/serializers/service_meta_serializer.rb
+++ b/app/serializers/service_meta_serializer.rb
@@ -1,5 +1,6 @@
 
 class ServiceMetaSerializer < ActiveModel::Serializer
+    attribute :label
     attribute :key
     attribute :value
 end
\ No newline at end of file
diff --git a/app/views/admin/custom_field_sections/_fields.html.erb b/app/views/admin/custom_field_sections/_fields.html.erb
index 38f81cd2..701c2d57 100644
--- a/app/views/admin/custom_field_sections/_fields.html.erb
+++ b/app/views/admin/custom_field_sections/_fields.html.erb
@@ -39,7 +39,7 @@
     <ul class="repeater__panels" aria-live="polite">
         <%= f.fields_for :custom_fields do |c| %>
             <li class="repeater__panel" data-custom-fields>
-                <%= render "admin/custom_field_sections/repeatable-fields", c: c %>
+                <%= render "admin/custom_field_sections/repeatable-fields", c: c, section: f.object %>
             </li>
         <% end %>
     </ul>
diff --git a/app/views/admin/custom_field_sections/_repeatable-fields.html.erb b/app/views/admin/custom_field_sections/_repeatable-fields.html.erb
index 0c413a81..efa2d02e 100644
--- a/app/views/admin/custom_field_sections/_repeatable-fields.html.erb
+++ b/app/views/admin/custom_field_sections/_repeatable-fields.html.erb
@@ -1,7 +1,7 @@
 <div class="field-group field-group--two-cols">
     <div class="field field--required">
-        <%= c.label :key, "Label", class: "field__label" %>
-        <%= c.text_field :key, class: "field__input" %>
+        <%= c.label :label, "Label", class: "field__label" %>
+        <%= c.text_field :label, class: "field__input" %>
     </div>
 
     <div class="field field--required">
@@ -20,6 +20,15 @@
     <%= c.text_area :hint, class: "field__input", rows: 1 %>
 </div>
 
+<% if @section.api_public %>
+<div class="field">
+    <%= c.label :key, "Key", class: "field__label" %>
+    <p class="field__hint">This is a unique field used to refer to this field in the API</p>
+    <%= c.text_field :key, class: "field__input", data: { slugify: true } %>
+</div>
+<% end %>
+
+
 <%= c.hidden_field :_destroy,  data: {destroy_field: true} %>
 
 <button type="button" class="repeater__closer" data-close="true" title="Remove this field">Remove this field</button>
\ No newline at end of file
diff --git a/app/views/admin/services/editors/_custom-fields.html.erb b/app/views/admin/services/editors/_custom-fields.html.erb
index e7a12746..d1367a83 100644
--- a/app/views/admin/services/editors/_custom-fields.html.erb
+++ b/app/views/admin/services/editors/_custom-fields.html.erb
@@ -8,20 +8,21 @@
         <% end %>
 
         <% section.custom_fields.each do |field| %>
-            <% meta = s.object.meta.find_or_initialize_by(key: field.key) %>
+            <% meta = s.object.meta.find_or_initialize_by(label: field.label, key: field.key) %>
             <%= s.fields_for :meta, meta do |c| %>
+                <%= c.hidden_field :label %>
                 <%= c.hidden_field :key %>
 
                 <% if field.field_type === "checkbox" %>
                     <div class="field">
                         <div class="checkbox">
                             <%= c.check_box :value, {class: "checkbox__input"}, "Yes", "No" %>
-                            <%= c.label :value, field.key, class: "checkbox__label" %>
+                            <%= c.label :value, field.label, class: "checkbox__label" %>
                         </div>
                     </div>
                 <% else %>
                     <div class="field">
-                        <%= c.label :value, field.key, class: "field__label" %>
+                        <%= c.label :value, field.label, class: "field__label" %>
                         <% if field.hint.present? %>
                             <p class="field__hint"><%= field.hint %></p>
                         <% end %>
diff --git a/config/routes.rb b/config/routes.rb
index c8bbf451..c50a37ef 100644
--- a/config/routes.rb
+++ b/config/routes.rb
@@ -82,6 +82,7 @@
       resources :accessibilities, only: [:index]
       get "me", to: "me#show"
       resources :services, only: [:index, :show]
+      resources :custom_fields, only: [:index]
     end
   end
 
diff --git a/db/migrate/20241111163850_add_custom_field_label.rb b/db/migrate/20241111163850_add_custom_field_label.rb
new file mode 100644
index 00000000..53b64363
--- /dev/null
+++ b/db/migrate/20241111163850_add_custom_field_label.rb
@@ -0,0 +1,14 @@
+class AddCustomFieldLabel < ActiveRecord::Migration[6.0]
+  def change
+    # Add new label column
+    add_column :custom_fields, :label, :string
+
+    # Copy data from key to label (only on up migration)
+    reversible do |dir|
+      dir.up do
+        CustomField.reset_column_information
+        CustomField.update_all('label = key')
+      end
+    end
+  end
+end
diff --git a/db/migrate/20241111181710_add_service_meta_label.rb b/db/migrate/20241111181710_add_service_meta_label.rb
new file mode 100644
index 00000000..7eab7033
--- /dev/null
+++ b/db/migrate/20241111181710_add_service_meta_label.rb
@@ -0,0 +1,14 @@
+class AddServiceMetaLabel < ActiveRecord::Migration[6.0]
+  def change
+     # Add new label column
+     add_column :service_meta, :label, :string
+
+     # Copy data from key to label (only on up migration)
+     reversible do |dir|
+       dir.up do
+         ServiceMeta.reset_column_information
+         ServiceMeta.update_all('label = key')
+       end
+     end
+  end
+end
diff --git a/db/schema.rb b/db/schema.rb
index 32ba2452..49336f1a 100644
--- a/db/schema.rb
+++ b/db/schema.rb
@@ -10,7 +10,7 @@
 #
 # It's strongly recommended that you check this file into your version control system.
 
-ActiveRecord::Schema.define(version: 2024_09_18_091413) do
+ActiveRecord::Schema.define(version: 2024_11_11_181710) do
 
   # These are extensions that must be enabled in order to support this database
   enable_extension "pg_trgm"
@@ -87,6 +87,7 @@
     t.string "hint"
     t.bigint "custom_field_section_id", null: false
     t.string "options"
+    t.string "label"
     t.index ["custom_field_section_id"], name: "index_custom_fields_on_custom_field_section_id"
   end
 
@@ -318,6 +319,7 @@
     t.string "value"
     t.datetime "created_at", precision: 6, null: false
     t.datetime "updated_at", precision: 6, null: false
+    t.string "label"
     t.index ["service_id"], name: "index_service_meta_on_service_id"
   end