EnvlopedDocs
Contacts

Contact fields

Define typed custom fields on your contacts so you can segment and personalise on them

Contact fields

A contact field is a typed definition for one key inside a contact's attributes object. Registering city as an enum and date_of_birth as a date is what turns free-form JSON into something you can segment on with real operators — date ranges, age windows, list membership — and what stops jkt and Jakarta becoming two different cities on import.

Every field key is also a merge tag: a field with key city can be referenced in a campaign as {{ city }}.

All endpoints require an Authorization: Bearer YOUR_API_KEY header.

The field object

{
  "id": "cf_abc123",
  "key": "date_of_birth",
  "label": "Date of birth",
  "type": "date",
  "options": null,
  "archived_at": null,
  "created_at": "2026-08-09T10:30:00Z",
  "updated_at": "2026-08-09T10:30:00Z"
}
FieldTypeDescription
keystringThe attributes key and the merge-tag name. Lowercase snake_case, starts with a letter. Immutable.
labelstringHuman label shown in the dashboard.
typestringOne of text, number, date, boolean, enum, list. Immutable.
optionsstring[] | nullFor enum and list: the allowed vocabulary. null means an open vocabulary.
archived_atstring | nullSet when the field is archived. Archived fields keep their data but disappear from pickers and can no longer be segmented on.

Types and what they accept

TypeStored asSegment operators
textstringeq, neq, contains, starts_with, is_set, is_not_set
numbernumbereq, neq, gt, gte, lt, lte, between
dateISO-8601 "YYYY-MM-DD" stringbefore, after, between, in_last_days, age_between
booleantrue / falseis_true, is_false
enumstring, must be in optionseq, neq, in, not_in
listarray of stringscontains_any, contains_all, not_contains

Age is never stored. Register a date field for the birthday and use age_between when segmenting — it is evaluated against today's date, so a contact leaves the "25–35" segment on their birthday with no intervention from you. See Segments.

List fields

GET /v1/contact-fields

Returns live fields first, then archived ones, each alphabetical by key.

{ "fields": [ /* field objects */ ] }

Create a field

POST /v1/contact-fields
ParameterTypeRequiredDescription
keystringYesLowercase snake_case, ≤ 63 characters.
labelstringYesHuman label.
typestringYesOne of the six types above.
optionsstring[]NoRequired vocabulary for enum / list; omit for an open vocabulary.
curl -X POST https://api.envloped.com/v1/contact-fields \
  -H "Authorization: Bearer en_abc123..." \
  -H "Content-Type: application/json" \
  -d '{
    "key": "city",
    "label": "City",
    "type": "enum",
    "options": ["Singapore", "Kuala Lumpur", "Jakarta", "Bangkok", "Manila"]
  }'

Creating a key that exists but is archived revives it rather than failing — otherwise an archived key would be burned forever.

Update a field

PATCH /v1/contact-fields/:id

label and options can change. key and type cannot: changing a type would reinterpret every value already stored under that key. To change a type, archive the field and create a new one.

Removing a value from an enum's options does not rewrite contacts that already hold it — it only stops new writes from using it.

Archive a field

DELETE /v1/contact-fields/:id

This archives, it never drops data. The values stay in contacts.attributes and reappear if the key is re-created. Segments that reference an archived field stop resolving and report unknown_field rather than silently matching nobody.

How validation works on write

Once a field exists, every write to attributes is checked against it:

  • Values are coerced to the field's type where unambiguous ("42"42 for a number, "true"true for a boolean).
  • A value that cannot be coerced is rejected.
  • An enum or list value outside a declared options vocabulary is rejected.
  • An empty or whitespace-only value clears the attribute to null rather than storing "" — which is what keeps is_set meaningful.
  • An unknown key — one with no registered field — is rejected. A typo'd key is how a segment quietly stops matching, so it is an error rather than a silent create.

On PATCH /v1/contacts/:id an invalid attribute returns 422. On POST /v1/contacts/batch the offending row is marked invalid and the rest of the batch still applies — one bad key must not discard 999 good contacts.

On this page