Create a contact field
/api/v1/contact-fieldsDeclares 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
labelstringrequiredThe display name.
keystringThe 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 | booleanHow a value should be EDITED and displayed. Not a storage type — every value is a string whatever this says, and nothing is coerced.
requiredbooleanWhether every contact must carry a value. Defaults to false.
default_valuestringSubstituted at send time for a contact with no value of their own. Never written to the contact.
help_textstringThe hint shown beside the input on a form. No behaviour.
Request
/api/v1/contact-fieldscurl -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"
}'const response = await fetch('https://app.mailyte.com/api/v1/contact-fields', {
method: 'POST',
headers: {
Authorization: 'Bearer mk_live_YOUR_API_KEY',
'Content-Type': 'application/json',
},
body: JSON.stringify({
"label": "Company",
"type": "text",
"required": true,
"help_text": "The organisation they work for"
}),
});
const { data } = await response.json();import requests
response = requests.post(
"https://app.mailyte.com/api/v1/contact-fields",
headers={"Authorization": "Bearer mk_live_YOUR_API_KEY"},
json={
"label": "Company",
"type": "text",
"required": true,
"help_text": "The organisation they work for"
},
)
data = response.json()["data"]<?php
$response = Http::withToken('mk_live_YOUR_API_KEY')
->post('https://app.mailyte.com/api/v1/contact-fields', [
'label' => 'Company',
'type' => 'text',
'required' => true,
'help_text' => 'The organisation they work for',
]);
$data = $response->json('data');require "net/http"
require "json"
uri = URI("https://app.mailyte.com/api/v1/contact-fields")
request = Net::HTTP::Post.new(uri)
request["Authorization"] = "Bearer mk_live_YOUR_API_KEY"
request["Content-Type"] = "application/json"
request.body = {
"label": "Company",
"type": "text",
"required": true,
"help_text": "The organisation they work for"
}.to_json
response = Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) { |http| http.request(request) }Response
Success.
dataobjectOne 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_fieldidstringThe 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.
keystringThe 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`.
declaredbooleanWhether 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.
labelstringDisplay name. Derived from the key for an undeclared one.
typestringtext | number | date | booleanHow 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.
requiredbooleanWhether 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_valuestringSubstituted at send time for contacts with no value of their own. It is never written into a contact's `fields`.
help_textstringThe 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_valueintegerHow 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
| Status | When |
|---|---|
401 | The API key is missing, unknown, revoked or expired. All four answer identically, on purpose: distinguishing them would confirm which keys exist. |
403 | The 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. |
404 | No such resource in this organization. |
422 | The request was understood but the values were not acceptable. |
429 | Too 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.