Custom fields
Custom fields extend the contact record with your own typed properties —
plan_tier, renewal_date, seat_count. Once defined, they’re settable on any
contact and usable in segment conditions and merge tags.
Over the API custom fields are read-only: you define the schema in the app,
under Settings, and read the definitions here so an integration can write the
right values under the right keys. Setting a value on a contact is done through
contacts via custom_fields, and needs contacts:write.
Required scopes#
| Endpoint | Scope |
|---|---|
GET /api/settings/custom-fields | custom-fields:read |
GET /api/settings/custom-fields/{id}/usage | custom-fields:read |
Read the definitions#
curl "https://app.example.com/t/acme/api/settings/custom-fields" \
-H "X-API-Key: $API_KEY"
Returns every field definition in the list envelope.
The field to key your integration on is field_key — that is the name you use in
a contact’s custom_fields object. field_type, enum_values and
value_constraint tell you what a write will be allowed to contain.
A sync that reads this at startup, rather than hard-coding keys, notices a field that was renamed or removed in the app before it starts writing values that go nowhere.
Field types#
string · number · boolean · date · datetime · time · enum ·
tags · email · url · timezone · country · currency · contact
Most are self-explanatory. Worth calling out:
enum— carriesenum_values, and pairs withvalue_constrainttags— multiple values per contact, unlikeenumcurrency— carries a 3-lettercurrency_codecontact— a reference to another contact, for relationships
value_constraint#
Applies to enum and string (text) fields — for every other type it is
strict and means nothing:
| Value | Behaviour |
|---|---|
strict | Only the listed enum_values are accepted |
open | New values are accepted and added to the list as they appear |
Read this before writing values. Against a strict field, a value outside
enum_values is rejected; against an open one, the same write succeeds and
extends the list. open is what a field owned by an upstream system usually
wants; strict is right when the list is the point.
Check what uses a field#
GET /api/settings/custom-fields/{id}/usage
Returns { usage }, keyed by source — contacts, segments, saved_filters,
automations — with a count for each; a source with no references may be
omitted. contacts counts how many contacts currently hold a value.
Useful for auditing before you retire a field in the app, and for knowing whether a field your integration stopped writing is still doing work elsewhere.
Working with values#
Values are set on the contact, not here:
curl -X PUT "https://app.example.com/t/acme/api/contacts" \
-H "X-API-Key: $API_KEY" \
-H 'Content-Type: application/json' \
-d '{"email":"ada@example.com","custom_fields":{"plan_tier":"pro"}}'
That needs contacts:write — not custom-fields:read.
Writing a value for a field_key that doesn’t exist returns UNKNOWN_FIELD —
it is an error, not an implicit definition. Fields are defined deliberately,
once, in the app, rather than invented by a sync.
Errors#
| Code | HTTP | Meaning |
|---|---|---|
NOT_FOUND | 404 | No field with that id. |
UNKNOWN_FIELD | 400 | A contact write referenced an undefined field_key. |
INVALID_FIELD_VALUE | 400 | A contact write sent a value the field’s type or enum_values does not allow. |
INSUFFICIENT_SCOPE | 403 | The key is missing custom-fields:read. |
See errors.md for the full envelope.