Custom Fields API
Extend the contact schema with typed fields specific to your business —lifetime_value, plan_tier, last_login_at, account_manager, etc. Custom fields are referenced from segment rules, flow conditions, message personalization, and the inbox UI.
Base path: /api/v1/custom-fields
Authentication: API key (X-API-Key) or session JWT.
Using the SDKs
The examples below hit the two endpoints you’ll combine most — defining a field and writing its value on a contact — so you can copy the header shape and envelope handling once and reuse it everywhere on this page. TypeScript uses the Node SDK’s typed helpers; Go, Ruby, and PHP call the same endpoints with plain HTTP clients, so no Orbit SDK dependency is required.client.request escape hatch shown above defines the field and sets it on a contact (source-only; vendor it from packages/sdk-python). Go, Ruby, and PHP use plain HTTP clients, so they run without an Orbit SDK.
Returns the typed ApiResponse envelope. See the SDK index at SDK quickstart.
Definitions
A field’s
key and type are immutable: they are fixed at creation and cannot be changed by PATCH (changing either would orphan the values already stored on contacts). To switch a field’s type, delete it and create a new one.
Worked sequences
Everything below the Definitions table works end to end: read one field, check what’s actually stored under it, update its presentation, reorder the list, then run the dependency scan before you delete. Typescript uses the SDK’s genericorbit.request escape hatch since the typed helpers wrap definitions only; Python/Go/Ruby/PHP call the same endpoints with plain HTTP.
dependencies first, and if any total is non-zero a plain DELETE returns 409 CUSTOM_FIELD_IN_USE carrying the same dependency snapshot in error.details.dependencies. Reissue the delete with ?force=true only after you’ve confirmed the listed segments, campaigns, flows, and contacts can survive losing the field.
Create a definition
POST /api/v1/custom-fields/ creates the definition and returns it with the server-assigned id, plus any options (required for select / multiselect) and validators you sent:
List definitions
GET /api/v1/custom-fields/ returns every definition in display order — display_order ascending, never-ordered fields trailing, then by key. A freshly provisioned tenant returns an empty list.
Update a definition
PATCH /api/v1/custom-fields/{id} renames the field or updates its description, options, default value, required/searchable flags, and validators. key and type stay frozen — pass null on a validator or the default value to clear it.
Reorder definitions
Pass the list’s definition ids top-to-bottom; the response returns the refreshed list so you can reconcile in one round-trip. Unknown ids are ignored, omitted ids keep their current position.Dependencies and explorer
Before you delete or repurpose a field, check what references it.Dependency scan
GET /api/v1/custom-fields/{id}/dependencies returns the count of contacts holding a value plus the segments, campaigns, drip enrollments, and flows that reference the field — the same snapshot the 409 below carries on a guarded delete:
Explorer payload
GET /api/v1/custom-fields/{id}/explorer returns the definition, the usage block (the same counts as the scan above, plus a total), and up to ten of the most recently written values — every sample is PII-redacted and tail-truncated to 120 characters:
Deleting a field that is in use
DELETE is guarded so you don’t silently break the segments, campaigns, and flows that reference a field.
If the field is still referenced by any segment, campaign, or flow — or has values stored on any contact — the request returns 409 Conflict with code CUSTOM_FIELD_IN_USE. The response details list every dependent so you can review or clean up first:
?force=true:
force=true the delete proceeds: the field’s values are stripped from every contact, and each dependent is flagged so you can find it afterward. Dependent segments are set to status: "broken". Dependent campaigns keep running — an in-flight send is never interrupted — but are flagged with a broken_custom_fields marker in their metadata. Dependent flows are set to status: "broken" so the automation runner skips them. The reference to the deleted field is not stripped from the campaign or flow filter: the filter JSON is left intact and only flagged, so the stale field-leaf remains until you edit or rebuild the filter. A field that is not referenced anywhere deletes without needing force.
Values
Per-contact values are written via a single endpoint and read inline as part of the contact resource.
The value is validated against the definition — a wrong type or a violate-the-validator value returns
422. A successful write echoes the contact id and key with ok: true, and the next contact GET reflects the new value because the write invalidates the contact-detail cache:
custom_fields object — keyed by the field key:
Example — define a lifetime-value field, then set it
See also
- Contacts API — read custom-field values inline