Create a contact field

POST/api/v1/contact-fields
Requires anorganization API keywith the scopecontacts:write

Declares one key of a contact's fields object: its label, its type, whether it is required, the value to fall back on and the hint to show beside it.

Declaring is optional. A contact may carry any attribute key you write, declared or not, and an undeclared key works in content and in segment rules exactly the same. What a declaration adds is a label, a default at send time, a place in the field picker, and the option of requiring it. declared on the field object tells you which kind you are looking at.

required is enforced in two places and only two. A contact being CREATED must supply it, and an update may not CLEAR a value that was already there. It is never checked retroactively, so marking a long-standing field required does not start rejecting contacts that predate the decision — which is the only way anyone could safely turn it on for a live audience. In an import, a row with no value for a required field comes back in errors[] with reason missing_required_field; the rest of the file still imports.

default_value does not satisfy required. The default is substituted at render time for a contact who has no value of their own and is never written to the contact. Accepting a placeholder for a field you marked required would be the opposite of what you asked for.

An organization may hold 100 field definitions. The 101st is refused with the count and the cap in the message.

Request body

  • labelstringrequired

    The display name.

  • keystring

    The stable name — the key inside a contact's `fields` and the `{{ tag }}` in content. Lowercase letters, numbers and underscores, starting with a letter. Omit it and we derive one from the label ("Signup Date" → `signup_date`). It cannot be changed afterwards: content and contact data both reference it.

  • typestringtext | number | date | boolean

    How a value should be EDITED and displayed. Not a storage type — every value is a string whatever this says, and nothing is coerced.

  • requiredboolean

    Whether every contact must carry a value. Defaults to false.

  • default_valuestring

    Substituted at send time for a contact with no value of their own. Never written to the contact.

  • help_textstring

    The hint shown beside the input on a form. No behaviour.

Request

POST/api/v1/contact-fields
curl -X POST 'https://app.mailyte.com/api/v1/contact-fields' \
  -H 'Authorization: Bearer mk_live_YOUR_API_KEY' \
  -H 'Content-Type: application/json' \
  -d '{
    "label": "Company",
    "type": "text",
    "required": true,
    "help_text": "The organisation they work for"
  }'
The key names its own organization, so no X-Organization-ID header is needed.

Response

Success.

  • dataobject

    One usable key of a contact's `fields` object. Serves both `GET /contact-fields` (the declared catalog) and `GET /contacts/attribute-keys` (the catalog plus keys observed on contact data), which is why `declared` exists.

    • objectstringcontact_field
    • idstring

      The definition's id, for `PUT`/`DELETE /contact-fields/{field}`. `null` when `declared` is false — there is no definition row, so there is nothing to address.

    • keystring

      The stable name. This is the key inside a contact's `fields` object and the `{{ signup_date }}` tag in campaign content. Contact values are keyed by this, never by `id`.

    • declaredboolean

      Whether this organization has DEFINED the key, or we merely observed it on contact data. Both are usable in content and in segment rules; only a declared one has a stored label, type and default. Every write path registers a definition for a key it has not seen before — `POST`, `PATCH` and `PUT /contacts` as well as `POST /contacts/import` — so which endpoint wrote a key no longer decides which side of this line it lands on. An undeclared key is one that arrived before that was true, or one whose name is not a legal key.

    • labelstring

      Display name. Derived from the key for an undeclared one.

    • typestringtext | number | date | boolean

      How a value should be EDITED and displayed. Not a storage type: every value in a contact's `fields` object is a string whatever this says, and nothing is coerced.

    • requiredboolean

      Whether every contact must carry a value. Enforced in two places and only two: a contact being CREATED must supply it, and an update may not CLEAR a value that was already there. It is never checked retroactively, so marking a field required does not start failing your sync on contacts that predate the decision — which is the only way anyone could safely turn it on for a live audience. `default_value` does not satisfy it: the default is a render-time placeholder, and accepting a placeholder for a field you marked required is the opposite of what you asked for. Always false for an undeclared key.

    • default_valuestring

      Substituted at send time for contacts with no value of their own. It is never written into a contact's `fields`.

    • help_textstring

      The hint shown beside this field on a form. `null` MEANS NONE WAS WRITTEN — it carries no behaviour, so an absent hint changes nothing about what the field accepts.

    • contacts_with_valueinteger

      How many contacts carry a non-empty value for this key — the "is it safe to delete this" signal. Always an integer.

    • created_atstring

      `null` when `declared` is false: an observed key has no definition row and therefore no creation time. Genuinely unknown, not zero.

Returned inside the standard envelope.

Errors

StatusWhen
401The API key is missing, unknown, revoked or expired. All four answer identically, on purpose: distinguishing them would confirm which keys exist.
403The key is valid but may not do this: it lacks the required scope, its IP allowlist does not include you, or this endpoint does not accept API keys.
404No such resource in this organization.
422The request was understood but the values were not acceptable.
429Too many requests, or the organization has spent its sending allowance. `Retry-After` says how long to wait.

Every status, with what causes it and what to do, is on the error reference.