Contact properties API
Custom fields for contacts, with a type and a default value.
Properties are the custom fields of your contacts, such as plan, city or birth date. Define the key and the type once; then use the key in properties when you create or update contacts, in imports and in the fields of forms. Each team can have up to 100 properties. In endpoints with {id}, you can use either the ID or the key.
Properties are also used in the contact batch and in the filters of dynamic segments (each type has its own operators). If you delete a property that a form uses, the form keeps receiving submissions, but the answer to that field is no longer saved on the contact.
#Types and conversion
| Type | Accepted values | Stored as |
|---|---|---|
string | Text, up to 1,000 characters. | Text, without leading or trailing spaces. |
number | 42, "42", "1234,56" or "1234.56". | Integer or decimal number. |
boolean | true/false, "sim"/"não", "yes"/"no", "1"/"0", "on"/"off". | true or false. |
date | "2024-03-18", "18/03/2024" (day first) or another recognizable date. | YYYY-MM-DD. |
An empty value (null or "") clears the property on the contact. Values that cannot be converted return 422.
#List properties
GET/contact-properties
Query parameters
limitintegerFrom 1 to 100. Default: 20.afterstringCursor for the next page.beforestringCursor for the previous page.
cURL
curl https://api.postal.so/contact-properties \
-H "Authorization: Bearer $POSTAL_API_KEY"
Node.js
const response = await fetch('https://api.postal.so/contact-properties', {
headers: {
Authorization: `Bearer ${process.env.POSTAL_API_KEY}`,
},
});
const data = await response.json();
Python
import os
import requests
response = requests.get(
"https://api.postal.so/contact-properties",
headers={"Authorization": f"Bearer {os.environ['POSTAL_API_KEY']}"},
timeout=10,
)
data = response.json()
PHP
$client = new \GuzzleHttp\Client(['base_uri' => 'https://api.postal.so']);
$response = $client->get('contact-properties', [
'headers' => ['Authorization' => 'Bearer '.getenv('POSTAL_API_KEY')],
]);
$data = json_decode($response->getBody(), true);
{
"object": "list",
"has_more": false,
"data": [
{
"object": "contact_property",
"id": "019a1d80-5d6e-7f80-8a9b-0c1d2e3f4a5b",
"key": "plan",
"type": "string",
"fallback_value": "free",
"created_at": "2026-10-20T09:00:00.000000Z",
"updated_at": "2026-10-20T09:00:00.000000Z"
}
]
}
#Create a property
POST/contact-properties
The key and the type cannot change later, because contacts already store values of that type.
Request body
keystringrequiredLowercase letters, numbers and_, starting with a letter, up to 64 characters. It cannot beid,email,external_id,user_id,first_name,last_name,unsubscribed,created_at,updated_at,properties,segments,topics,tagsorsource.typestringrequiredstring,number,booleanordate.fallback_valuestring | number | boolean | nullValue used when the contact does not have the property (for example, when personalizing emails).
Errors
- 422
validation_errorInvalid, reserved or already used key, invalid type, default value that does not match the type, or the team already has 100 properties.
cURL
curl -X POST https://api.postal.so/contact-properties \
-H "Authorization: Bearer $POSTAL_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"key": "plan",
"type": "string",
"fallback_value": "free"
}'
Node.js
const response = await fetch('https://api.postal.so/contact-properties', {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.POSTAL_API_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
key: 'plan',
type: 'string',
fallback_value: 'free',
}),
});
const data = await response.json();
Python
import os
import requests
response = requests.post(
"https://api.postal.so/contact-properties",
headers={"Authorization": f"Bearer {os.environ['POSTAL_API_KEY']}"},
json={
"key": "plan",
"type": "string",
"fallback_value": "free",
},
timeout=10,
)
data = response.json()
PHP
$client = new \GuzzleHttp\Client(['base_uri' => 'https://api.postal.so']);
$response = $client->post('contact-properties', [
'headers' => ['Authorization' => 'Bearer '.getenv('POSTAL_API_KEY')],
'json' => [
'key' => 'plan',
'type' => 'string',
'fallback_value' => 'free',
],
]);
$data = json_decode($response->getBody(), true);
{
"object": "contact_property",
"id": "019a1d80-5d6e-7f80-8a9b-0c1d2e3f4a5b"
}
#Get a property
GET/contact-properties/{id}
Path parameters
idstringrequiredID or key.
Errors
- 404
not_foundThe resource does not exist or belongs to another team.
cURL
curl https://api.postal.so/contact-properties/plan \
-H "Authorization: Bearer $POSTAL_API_KEY"
Node.js
const response = await fetch('https://api.postal.so/contact-properties/plan', {
headers: {
Authorization: `Bearer ${process.env.POSTAL_API_KEY}`,
},
});
const data = await response.json();
Python
import os
import requests
response = requests.get(
"https://api.postal.so/contact-properties/plan",
headers={"Authorization": f"Bearer {os.environ['POSTAL_API_KEY']}"},
timeout=10,
)
data = response.json()
PHP
$client = new \GuzzleHttp\Client(['base_uri' => 'https://api.postal.so']);
$response = $client->get('contact-properties/plan', [
'headers' => ['Authorization' => 'Bearer '.getenv('POSTAL_API_KEY')],
]);
$data = json_decode($response->getBody(), true);
{
"object": "contact_property",
"id": "019a1d80-5d6e-7f80-8a9b-0c1d2e3f4a5b",
"key": "plan",
"type": "string",
"fallback_value": "free",
"created_at": "2026-10-20T09:00:00.000000Z",
"updated_at": "2026-10-20T09:00:00.000000Z"
}
#Update a property
PATCH/contact-properties/{id}
Only the default value can change. Sending key or type returns 422.
Path parameters
idstringrequiredID or key.
Request body
fallback_valuestring | number | boolean | nullNew default value;nullremoves it.
Errors
- 422
validation_errorA field is invalid. The message says which one. - 404
not_foundThe resource does not exist or belongs to another team.
cURL
curl -X PATCH https://api.postal.so/contact-properties/plan \
-H "Authorization: Bearer $POSTAL_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"fallback_value": "pro"
}'
Node.js
const response = await fetch('https://api.postal.so/contact-properties/plan', {
method: 'PATCH',
headers: {
Authorization: `Bearer ${process.env.POSTAL_API_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
fallback_value: 'pro',
}),
});
const data = await response.json();
Python
import os
import requests
response = requests.patch(
"https://api.postal.so/contact-properties/plan",
headers={"Authorization": f"Bearer {os.environ['POSTAL_API_KEY']}"},
json={
"fallback_value": "pro",
},
timeout=10,
)
data = response.json()
PHP
$client = new \GuzzleHttp\Client(['base_uri' => 'https://api.postal.so']);
$response = $client->patch('contact-properties/plan', [
'headers' => ['Authorization' => 'Bearer '.getenv('POSTAL_API_KEY')],
'json' => [
'fallback_value' => 'pro',
],
]);
$data = json_decode($response->getBody(), true);
{
"object": "contact_property",
"id": "019a1d80-5d6e-7f80-8a9b-0c1d2e3f4a5b"
}
#Delete a property
DELETE/contact-properties/{id}
Removes the definition immediately; the values the contacts had are deleted in the background.
Path parameters
idstringrequiredID or key.
Errors
- 404
not_foundThe resource does not exist or belongs to another team.
cURL
curl -X DELETE https://api.postal.so/contact-properties/plan \
-H "Authorization: Bearer $POSTAL_API_KEY"
Node.js
const response = await fetch('https://api.postal.so/contact-properties/plan', {
method: 'DELETE',
headers: {
Authorization: `Bearer ${process.env.POSTAL_API_KEY}`,
},
});
const data = await response.json();
Python
import os
import requests
response = requests.delete(
"https://api.postal.so/contact-properties/plan",
headers={"Authorization": f"Bearer {os.environ['POSTAL_API_KEY']}"},
timeout=10,
)
data = response.json()
PHP
$client = new \GuzzleHttp\Client(['base_uri' => 'https://api.postal.so']);
$response = $client->delete('contact-properties/plan', [
'headers' => ['Authorization' => 'Bearer '.getenv('POSTAL_API_KEY')],
]);
$data = json_decode($response->getBody(), true);
{
"object": "contact_property",
"id": "019a1d80-5d6e-7f80-8a9b-0c1d2e3f4a5b",
"deleted": true
}