Custom field definitions API

The endpoints under /api/custom-fields/ manage the schema for custom fields — the field definitions admins create in the settings UI. The actual values you store on individual records use each entity's regular endpoint (see Custom fields feature).

Permissions

  • List / read — any authenticated user (the frontend needs definitions to render detail-page forms).
  • Create / update / delete — admin role only.

List definitions

GET /api/custom-fields/?target_model=Lead&active_only=true
Authorization: Bearer <token>
{
  "definitions": [
    {
      "id": 17,
      "target_model": "Lead",
      "key": "industry",
      "label": "Industry",
      "field_type": "dropdown",
      "options": [
        { "value": "saas", "label": "SaaS" },
        { "value": "manufacturing", "label": "Manufacturing" }
      ],
      "is_required": false,
      "is_filterable": true,
      "is_active": true,
      "display_order": 0
    }
  ]
}

Query parameters:

  • target_model — filter by entity name (Lead, Account, Contact, Opportunity, Task, Case, Invoice, Estimate, RecurringInvoice).
  • active_only=true — exclude soft-deleted definitions.

Create

POST /api/custom-fields/
{
  "target_model": "Lead",
  "key": "industry",
  "label": "Industry",
  "field_type": "dropdown",
  "options": [
    { "value": "saas", "label": "SaaS" },
    { "value": "manufacturing", "label": "Manufacturing" }
  ],
  "is_required": false,
  "is_filterable": true,
  "display_order": 0,
  "is_active": true
}

Constraints:

  • key must match ^[a-z][a-z0-9_]*$ and be unique per (org, target_model).
  • options is required only when field_type is dropdown.
  • target_model must be one of the nine supported entities.

Update

PUT /api/custom-fields/<id>/
{ "label": "Industry Vertical", "options": [ … ] }

Immutable fields: key and field_type cannot change after creation. The API returns 400 if either is in the body.

Delete (soft)

DELETE /api/custom-fields/<id>/

Sets is_active=false. Existing values on records are preserved; new writes for the deactivated key are rejected as unknown. Reactivate with PATCH { "is_active": true }.

Errors

{
  "error": true,
  "errors": {
    "key": "must match ^[a-z][a-z0-9_]*$",
    "options": "Dropdown fields require a non-empty list of {value, label} pairs"
  }
}

See Errors for the full error response shape.