SonarSend API Contacts & audience Custom fields

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#

EndpointScope
GET /api/settings/custom-fieldscustom-fields:read
GET /api/settings/custom-fields/{id}/usagecustom-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 — carries enum_values, and pairs with value_constraint
  • tags — multiple values per contact, unlike enum
  • currency — carries a 3-letter currency_code
  • contact — 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:

ValueBehaviour
strictOnly the listed enum_values are accepted
openNew 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#

CodeHTTPMeaning
NOT_FOUND404No field with that id.
UNKNOWN_FIELD400A contact write referenced an undefined field_key.
INVALID_FIELD_VALUE400A contact write sent a value the field’s type or enum_values does not allow.
INSUFFICIENT_SCOPE403The key is missing custom-fields:read.

See errors.md for the full envelope.