Reference
Custom Fields
Custom fields extend contacts with user-defined attributes. The type controls
UI rendering and validation. For dropdown, options must be provided.
List custom fields
Returns all custom fields for the authenticated user's location, ordered by
sort_order then alphabetically.
GET
/api/custom-fields
curl --request GET \
--get "https://klozzo.com/api/custom-fields" \
--header "Authorization: Bearer {YOUR_API_TOKEN}" \
--header "Content-Type: application/json" \
--header "Accept: application/json"
const url = new URL(
"https://klozzo.com/api/custom-fields"
);
const headers = {
"Authorization": "Bearer {YOUR_API_TOKEN}",
"Content-Type": "application/json",
"Accept": "application/json",
};
fetch(url, {
method: "GET",
headers,
}).then(response => response.json());
$client = new \GuzzleHttp\Client();
$url = 'https://klozzo.com/api/custom-fields';
$response = $client->get(
$url,
[
'headers' => [
'Authorization' => 'Bearer {YOUR_API_TOKEN}',
'Content-Type' => 'application/json',
'Accept' => 'application/json',
],
]
);
$body = $response->getBody();
print_r(json_decode((string) $body));
import requests
import json
url = 'https://klozzo.com/api/custom-fields'
headers = {
'Authorization': 'Bearer {YOUR_API_TOKEN}',
'Content-Type': 'application/json',
'Accept': 'application/json'
}
response = requests.request('GET', url, headers=headers)
response.json()
{
"data": [
{
"id": null,
"key": null,
"label": null,
"name": "quidem",
"type": "text",
"write_policy": "fill_if_empty",
"options": null,
"default_value": null,
"is_required": false,
"sort_order": 6,
"created_at": null,
"updated_at": null
},
{
"id": null,
"key": null,
"label": null,
"name": "autem",
"type": "date",
"write_policy": "fill_if_empty",
"options": null,
"default_value": null,
"is_required": false,
"sort_order": 0,
"created_at": null,
"updated_at": null
}
]
}
-
dataobject[]-
idstring -
keystring -
labelstring -
namestring -
typestring -
write_policystring -
optionsstring -
default_valuestring -
is_requiredboolean -
sort_orderinteger -
created_atstring -
updated_atstring
-
Create a custom field
Adds a property every contact in this location can carry. Create the field
before you start sending its values: POST /api/leads keeps a value
whose key does not exist yet but does not create the field, and
PUT /api/contacts/{id}/custom-field-values rejects it outright.
POST
/api/custom-fields
The key is what the API uses and it is immutable — renaming the
field later changes what people see, never what your code sends. Choose it
deliberately, or let it be derived from the name and read it back from the
response.
write_policy is the field's own rule for what happens when a new value
arrives for one that already has one. It is the reason a lead capture can
enrich a record without ever destroying what a seller typed by hand.
Body parameters
-
namestring requiredDisplay name (max 255).
Example:
Lifecycle stage -
keystringStable, immutable identifier used by the API (lowercase a-z, 0-9, _). Derived from
nameif omitted.Example:
lifecycle_stage -
typestring requiredOne of
text,number,date,dropdown,checkbox,url,phone,email.Example:
dropdown -
write_policystringWhat happens when a new value arrives for this field. One of
fill_if_empty(default — only fills a blank, never overwrites),last_write_wins(newest value always wins),immutable(written once, then locked).Example:
fill_if_empty -
optionsstring[]Required when
type=dropdown; the available choices.Example:
["architecto"] -
default_valuestringDefault applied to new contacts.
Example:
trial -
is_requiredbooleanWhether this field must be filled.
Example:
false -
sort_orderintegerDisplay order in forms (low values first).
Example:
0
curl --request POST \
"https://klozzo.com/api/custom-fields" \
--header "Authorization: Bearer {YOUR_API_TOKEN}" \
--header "Content-Type: application/json" \
--header "Accept: application/json" \
--data "{
\"name\": \"Lifecycle stage\",
\"key\": \"lifecycle_stage\",
\"type\": \"dropdown\",
\"write_policy\": \"fill_if_empty\",
\"options\": [
\"architecto\"
],
\"default_value\": \"trial\",
\"is_required\": false,
\"sort_order\": 0
}"
const url = new URL(
"https://klozzo.com/api/custom-fields"
);
const headers = {
"Authorization": "Bearer {YOUR_API_TOKEN}",
"Content-Type": "application/json",
"Accept": "application/json",
};
let body = {
"name": "Lifecycle stage",
"key": "lifecycle_stage",
"type": "dropdown",
"write_policy": "fill_if_empty",
"options": [
"architecto"
],
"default_value": "trial",
"is_required": false,
"sort_order": 0
};
fetch(url, {
method: "POST",
headers,
body: JSON.stringify(body),
}).then(response => response.json());
$client = new \GuzzleHttp\Client();
$url = 'https://klozzo.com/api/custom-fields';
$response = $client->post(
$url,
[
'headers' => [
'Authorization' => 'Bearer {YOUR_API_TOKEN}',
'Content-Type' => 'application/json',
'Accept' => 'application/json',
],
'json' => [
'name' => 'Lifecycle stage',
'key' => 'lifecycle_stage',
'type' => 'dropdown',
'write_policy' => 'fill_if_empty',
'options' => ['architecto'],
'default_value' => 'trial',
'is_required' => false,
'sort_order' => 0,
],
]
);
$body = $response->getBody();
print_r(json_decode((string) $body));
import requests
import json
url = 'https://klozzo.com/api/custom-fields'
payload = {
"name": "Lifecycle stage",
"key": "lifecycle_stage",
"type": "dropdown",
"write_policy": "fill_if_empty",
"options": [
"architecto"
],
"default_value": "trial",
"is_required": false,
"sort_order": 0
}
headers = {
'Authorization': 'Bearer {YOUR_API_TOKEN}',
'Content-Type': 'application/json',
'Accept': 'application/json'
}
response = requests.request('POST', url, headers=headers, json=payload)
response.json()
{
"data": {
"id": null,
"key": null,
"label": null,
"name": "eius",
"type": "phone",
"write_policy": "fill_if_empty",
"options": null,
"default_value": null,
"is_required": false,
"sort_order": 10,
"created_at": null,
"updated_at": null
}
}
-
dataobject-
idstring -
keystring -
labelstring -
namestring -
typestring -
write_policystring -
optionsstring -
default_valuestring -
is_requiredboolean -
sort_orderinteger -
created_atstring -
updated_atstring
-
Fetch a custom field
Returns one custom field by its UUID: its key, its type, its options if
it is a
dropdown, and its write_policy. Read it when a value you sent did not
land the way you expected — nine times out of ten the answer is in
write_policy, not in your payload.
GET
/api/custom-fields/{uuid}
Path parameters
-
uuidinteger requiredCustom field ID.
Example:
9
curl --request GET \
--get "https://klozzo.com/api/custom-fields/9" \
--header "Authorization: Bearer {YOUR_API_TOKEN}" \
--header "Content-Type: application/json" \
--header "Accept: application/json"
const url = new URL(
"https://klozzo.com/api/custom-fields/9"
);
const headers = {
"Authorization": "Bearer {YOUR_API_TOKEN}",
"Content-Type": "application/json",
"Accept": "application/json",
};
fetch(url, {
method: "GET",
headers,
}).then(response => response.json());
$client = new \GuzzleHttp\Client();
$url = 'https://klozzo.com/api/custom-fields/9';
$response = $client->get(
$url,
[
'headers' => [
'Authorization' => 'Bearer {YOUR_API_TOKEN}',
'Content-Type' => 'application/json',
'Accept' => 'application/json',
],
]
);
$body = $response->getBody();
print_r(json_decode((string) $body));
import requests
import json
url = 'https://klozzo.com/api/custom-fields/9'
headers = {
'Authorization': 'Bearer {YOUR_API_TOKEN}',
'Content-Type': 'application/json',
'Accept': 'application/json'
}
response = requests.request('GET', url, headers=headers)
response.json()
{
"data": {
"id": null,
"key": null,
"label": null,
"name": "nostrum",
"type": "number",
"write_policy": "fill_if_empty",
"options": null,
"default_value": null,
"is_required": false,
"sort_order": 6,
"created_at": null,
"updated_at": null
}
}
-
dataobject-
idstring -
keystring -
labelstring -
namestring -
typestring -
write_policystring -
optionsstring -
default_valuestring -
is_requiredboolean -
sort_orderinteger -
created_atstring -
updated_atstring
-
{
"message": "No query results for model."
}
Every error shares the same shape — message and errors.
See Errors.
Update a custom field
Updates a custom field definition by its UUID. Renaming or changing type
does not migrate existing stored values; callers
are responsible for downstream cleanup. The key identifier is immutable.
PUT
/api/custom-fields/{uuid}
Path parameters
-
uuidinteger requiredCustom field ID.
Example:
9
Body parameters
-
namestringDisplay name.
Example:
Lifecycle stage -
labelstringMust not be greater than 255 characters.
Example:
n -
typestringOne of the supported types (see create).
Example:
architecto -
write_policystringOne of
immutable,fill_if_empty,last_write_winsExample:
last_write_wins -
optionsstring[]Required when
type=dropdown.Example:
["architecto"] -
default_valuestringDefault applied to new contacts.
Example:
architecto -
is_requiredbooleanWhether a contact form refuses to save without this field. Does not apply retroactively to contacts that already exist.
Example:
false -
sort_orderintegerWhere the field sits in forms; low values first.
Example:
0
curl --request PUT \
"https://klozzo.com/api/custom-fields/9" \
--header "Authorization: Bearer {YOUR_API_TOKEN}" \
--header "Content-Type: application/json" \
--header "Accept: application/json" \
--data "{
\"name\": \"Lifecycle stage\",
\"label\": \"n\",
\"type\": \"architecto\",
\"write_policy\": \"last_write_wins\",
\"options\": [
\"architecto\"
],
\"default_value\": \"architecto\",
\"is_required\": false,
\"sort_order\": 0
}"
const url = new URL(
"https://klozzo.com/api/custom-fields/9"
);
const headers = {
"Authorization": "Bearer {YOUR_API_TOKEN}",
"Content-Type": "application/json",
"Accept": "application/json",
};
let body = {
"name": "Lifecycle stage",
"label": "n",
"type": "architecto",
"write_policy": "last_write_wins",
"options": [
"architecto"
],
"default_value": "architecto",
"is_required": false,
"sort_order": 0
};
fetch(url, {
method: "PUT",
headers,
body: JSON.stringify(body),
}).then(response => response.json());
$client = new \GuzzleHttp\Client();
$url = 'https://klozzo.com/api/custom-fields/9';
$response = $client->put(
$url,
[
'headers' => [
'Authorization' => 'Bearer {YOUR_API_TOKEN}',
'Content-Type' => 'application/json',
'Accept' => 'application/json',
],
'json' => [
'name' => 'Lifecycle stage',
'label' => 'n',
'type' => 'architecto',
'write_policy' => 'last_write_wins',
'options' => ['architecto'],
'default_value' => 'architecto',
'is_required' => false,
'sort_order' => 0,
],
]
);
$body = $response->getBody();
print_r(json_decode((string) $body));
import requests
import json
url = 'https://klozzo.com/api/custom-fields/9'
payload = {
"name": "Lifecycle stage",
"label": "n",
"type": "architecto",
"write_policy": "last_write_wins",
"options": [
"architecto"
],
"default_value": "architecto",
"is_required": false,
"sort_order": 0
}
headers = {
'Authorization': 'Bearer {YOUR_API_TOKEN}',
'Content-Type': 'application/json',
'Accept': 'application/json'
}
response = requests.request('PUT', url, headers=headers, json=payload)
response.json()
{
"data": {
"id": null,
"key": null,
"label": null,
"name": "et",
"type": "url",
"write_policy": "fill_if_empty",
"options": null,
"default_value": null,
"is_required": false,
"sort_order": 3,
"created_at": null,
"updated_at": null
}
}
-
dataobject-
idstring -
keystring -
labelstring -
namestring -
typestring -
write_policystring -
optionsstring -
default_valuestring -
is_requiredboolean -
sort_orderinteger -
created_atstring -
updated_atstring
-
{
"message": "Type no es una opción válida.",
"errors": {
"type": [
"Type no es una opción válida."
]
}
}
Every error shares the same shape — message and errors.
See Errors.
{
"message": "Falta options cuando type es dropdown.",
"errors": {
"options": [
"Falta options cuando type es dropdown."
]
}
}
Every error shares the same shape — message and errors.
See Errors.
{
"message": "El valor de key ya está en uso.",
"errors": {
"key": [
"El valor de key ya está en uso."
]
}
}
Every error shares the same shape — message and errors.
See Errors.
Delete a custom field
Deletes a custom field definition by its UUID, so the field stops being offered anywhere in the CRM. Stored values on existing contacts are not removed automatically (orphaned values remain queryable by ID).
DELETE
/api/custom-fields/{uuid}
Path parameters
-
uuidinteger requiredCustom field ID.
Example:
9
curl --request DELETE \
"https://klozzo.com/api/custom-fields/9" \
--header "Authorization: Bearer {YOUR_API_TOKEN}" \
--header "Content-Type: application/json" \
--header "Accept: application/json"
const url = new URL(
"https://klozzo.com/api/custom-fields/9"
);
const headers = {
"Authorization": "Bearer {YOUR_API_TOKEN}",
"Content-Type": "application/json",
"Accept": "application/json",
};
fetch(url, {
method: "DELETE",
headers,
}).then(response => response.json());
$client = new \GuzzleHttp\Client();
$url = 'https://klozzo.com/api/custom-fields/9';
$response = $client->delete(
$url,
[
'headers' => [
'Authorization' => 'Bearer {YOUR_API_TOKEN}',
'Content-Type' => 'application/json',
'Accept' => 'application/json',
],
]
);
$body = $response->getBody();
print_r(json_decode((string) $body));
import requests
import json
url = 'https://klozzo.com/api/custom-fields/9'
headers = {
'Authorization': 'Bearer {YOUR_API_TOKEN}',
'Content-Type': 'application/json',
'Accept': 'application/json'
}
response = requests.request('DELETE', url, headers=headers)
response.json()
{
"message": "Custom field deleted."
}
-
messagestring