Contatos
As pessoas da sua audiência: dados, propriedades, segmentos e inscrição em tópicos.
Contatos são as pessoas da sua audiência de marketing: e-mail, nome, propriedades personalizadas, segmentos e a inscrição em tópicos. Cada e-mail aparece uma vez por time. O número de contatos depende do plano de marketing (Free: 1.000, Pro: 5.000, Scale: 50.000, Enterprise: sem limite). Nos endpoints com {id}, você pode usar o ID ou o próprio e-mail do contato. Para trazer muitos contatos de uma vez, use a importação de CSV.
#Listar contatos
GET/contacts
Do mais recente para o mais antigo. A lista não traz os segmentos de cada contato.
Parâmetros de consulta
limitintegerDe 1 a 100. Padrão: 20.afterstringCursor da próxima página.beforestringCursor da página anterior.searchstringBusca no e-mail e no nome.segment_idstringSó contatos de um segmento.topic_idstringSó contatos inscritos no tópico: os que escolheram receber e, se o tópico foropt_inpor padrão, os que não escolheram nada.unsubscribedbooleantruepara os descadastrados;falsepara os demais.
Erros
- 422
validation_errorAlgum campo é inválido. A mensagem diz qual.
cURL
curl 'https://api.postal.so/contacts?segment_id=019a1d90-3b4c-7d5e-9f6a-7b8c9d0e1f2a&unsubscribed=false' \
-H "Authorization: Bearer $POSTAL_API_KEY"
Node.js
const response = await fetch('https://api.postal.so/contacts?segment_id=019a1d90-3b4c-7d5e-9f6a-7b8c9d0e1f2a&unsubscribed=false', {
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/contacts",
headers={"Authorization": f"Bearer {os.environ['POSTAL_API_KEY']}"},
params={
"segment_id": "019a1d90-3b4c-7d5e-9f6a-7b8c9d0e1f2a",
"unsubscribed": "false",
},
timeout=10,
)
data = response.json()
PHP
$client = new \GuzzleHttp\Client(['base_uri' => 'https://api.postal.so']);
$response = $client->get('contacts', [
'headers' => ['Authorization' => 'Bearer '.getenv('POSTAL_API_KEY')],
'query' => [
'segment_id' => '019a1d90-3b4c-7d5e-9f6a-7b8c9d0e1f2a',
'unsubscribed' => 'false',
],
]);
$data = json_decode($response->getBody(), true);
{
"object": "list",
"has_more": true,
"data": [
{
"object": "contact",
"id": "019a1e00-1a2b-7c3d-8e4f-5a6b7c8d9e0f",
"email": "[email protected]",
"first_name": "Ana",
"last_name": "Souza",
"unsubscribed": false,
"properties": {
"plan": "pro",
"city": "Recife",
"customer_since": "2024-03-18"
},
"created_at": "2026-10-24T14:00:00.000000Z",
"updated_at": "2026-10-24T14:00:00.000000Z"
}
]
}
#Criar um contato
POST/contacts
Cria o contato e, se quiser, já o coloca em segmentos e registra a escolha dele nos tópicos. Dispara o webhook contact.created.
Corpo da requisição
emailstringobrigatórioE-mail, até 320 caracteres. Guardado em minúsculas.first_namestring | nullNome, até 255 caracteres.last_namestring | nullSobrenome, até 255 caracteres.unsubscribedbooleanDescadastrado de todos os envios de marketing. Padrão:false.propertiesobjectValores das propriedades, como{"plan": "pro"}. Cada chave precisa existir antes; os valores são convertidos para o tipo da propriedade.segmentsstring[]IDs de até 100 segmentos (ou objetos{"id": "…"}).topicsobject[]Até 100 escolhas de tópicos.idstringobrigatórioID do tópico.subscriptionstringobrigatórioopt_in(quer receber) ouopt_out(não quer).
Erros
- 422
validation_errorE-mail inválido ou já cadastrado, propriedade desconhecida ou valor com o tipo errado. - 403
plan_limit_reachedO plano não permite mais contatos. - 404
not_foundAlgum segmento ou tópico não existe.
cURL
curl -X POST https://api.postal.so/contacts \
-H "Authorization: Bearer $POSTAL_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"email": "[email protected]",
"first_name": "Ana",
"last_name": "Souza",
"properties": {
"plan": "pro",
"city": "Recife"
},
"segments": ["019a1d90-3b4c-7d5e-9f6a-7b8c9d0e1f2a"],
"topics": [
{
"id": "019a1d95-4c5d-7e6f-8a7b-8c9d0e1f2a3b",
"subscription": "opt_in"
}
]
}'
Node.js
const response = await fetch('https://api.postal.so/contacts', {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.POSTAL_API_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
email: '[email protected]',
first_name: 'Ana',
last_name: 'Souza',
properties: {
plan: 'pro',
city: 'Recife',
},
segments: ['019a1d90-3b4c-7d5e-9f6a-7b8c9d0e1f2a'],
topics: [
{
id: '019a1d95-4c5d-7e6f-8a7b-8c9d0e1f2a3b',
subscription: 'opt_in',
},
],
}),
});
const data = await response.json();
Python
import os
import requests
response = requests.post(
"https://api.postal.so/contacts",
headers={"Authorization": f"Bearer {os.environ['POSTAL_API_KEY']}"},
json={
"email": "[email protected]",
"first_name": "Ana",
"last_name": "Souza",
"properties": {
"plan": "pro",
"city": "Recife",
},
"segments": ["019a1d90-3b4c-7d5e-9f6a-7b8c9d0e1f2a"],
"topics": [
{
"id": "019a1d95-4c5d-7e6f-8a7b-8c9d0e1f2a3b",
"subscription": "opt_in",
},
],
},
timeout=10,
)
data = response.json()
PHP
$client = new \GuzzleHttp\Client(['base_uri' => 'https://api.postal.so']);
$response = $client->post('contacts', [
'headers' => ['Authorization' => 'Bearer '.getenv('POSTAL_API_KEY')],
'json' => [
'email' => '[email protected]',
'first_name' => 'Ana',
'last_name' => 'Souza',
'properties' => [
'plan' => 'pro',
'city' => 'Recife',
],
'segments' => ['019a1d90-3b4c-7d5e-9f6a-7b8c9d0e1f2a'],
'topics' => [
[
'id' => '019a1d95-4c5d-7e6f-8a7b-8c9d0e1f2a3b',
'subscription' => 'opt_in',
],
],
],
]);
$data = json_decode($response->getBody(), true);
{
"object": "contact",
"id": "019a1e00-1a2b-7c3d-8e4f-5a6b7c8d9e0f"
}
#Obter um contato
GET/contacts/{id}
Devolve o contato com os segmentos de que ele faz parte.
Parâmetros de caminho
idstringobrigatórioID ou e-mail do contato.
Erros
- 404
not_foundO recurso não existe ou é de outro time.
cURL
curl https://api.postal.so/contacts/[email protected] \
-H "Authorization: Bearer $POSTAL_API_KEY"
Node.js
const response = await fetch('https://api.postal.so/contacts/[email protected]', {
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/contacts/[email protected]",
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('contacts/[email protected]', [
'headers' => ['Authorization' => 'Bearer '.getenv('POSTAL_API_KEY')],
]);
$data = json_decode($response->getBody(), true);
{
"object": "contact",
"id": "019a1e00-1a2b-7c3d-8e4f-5a6b7c8d9e0f",
"email": "[email protected]",
"first_name": "Ana",
"last_name": "Souza",
"unsubscribed": false,
"properties": {
"plan": "pro",
"city": "Recife",
"customer_since": "2024-03-18"
},
"created_at": "2026-10-24T14:00:00.000000Z",
"updated_at": "2026-10-24T14:00:00.000000Z",
"segments": [
{
"id": "019a1d90-3b4c-7d5e-9f6a-7b8c9d0e1f2a",
"name": "Clientes ativos"
}
]
}
#Atualizar um contato
PATCH/contacts/{id}
Só os campos enviados mudam. Em properties, as chaves enviadas são atualizadas e as outras ficam como estão; mande null ou "" para apagar um valor. Segmentos e tópicos têm endpoints próprios (abaixo). Se algo mudou, dispara contact.updated.
Parâmetros de caminho
idstringobrigatórioID ou e-mail do contato.
Corpo da requisição (todos opcionais)
emailstringNovo e-mail.first_namestring | nullNome.last_namestring | nullSobrenome.unsubscribedbooleanDescadastra (true) ou reinscreve (false).propertiesobjectValores a mudar.
Erros
- 422
validation_errorE-mail já usado por outro contato, propriedade desconhecida, valor inválido, ousegments/topicsno corpo. - 404
not_foundO recurso não existe ou é de outro time.
cURL
curl -X PATCH https://api.postal.so/contacts/019a1e00-1a2b-7c3d-8e4f-5a6b7c8d9e0f \
-H "Authorization: Bearer $POSTAL_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"properties": {
"plan": "scale",
"city": null
}
}'
Node.js
const response = await fetch('https://api.postal.so/contacts/019a1e00-1a2b-7c3d-8e4f-5a6b7c8d9e0f', {
method: 'PATCH',
headers: {
Authorization: `Bearer ${process.env.POSTAL_API_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
properties: {
plan: 'scale',
city: null,
},
}),
});
const data = await response.json();
Python
import os
import requests
response = requests.patch(
"https://api.postal.so/contacts/019a1e00-1a2b-7c3d-8e4f-5a6b7c8d9e0f",
headers={"Authorization": f"Bearer {os.environ['POSTAL_API_KEY']}"},
json={
"properties": {
"plan": "scale",
"city": None,
},
},
timeout=10,
)
data = response.json()
PHP
$client = new \GuzzleHttp\Client(['base_uri' => 'https://api.postal.so']);
$response = $client->patch('contacts/019a1e00-1a2b-7c3d-8e4f-5a6b7c8d9e0f', [
'headers' => ['Authorization' => 'Bearer '.getenv('POSTAL_API_KEY')],
'json' => [
'properties' => [
'plan' => 'scale',
'city' => null,
],
],
]);
$data = json_decode($response->getBody(), true);
{
"object": "contact",
"id": "019a1e00-1a2b-7c3d-8e4f-5a6b7c8d9e0f"
}
#Excluir um contato
DELETE/contacts/{id}
Remove o contato da audiência, com os segmentos e as escolhas de tópicos. Dispara contact.deleted. Para só parar de enviar, prefira unsubscribed: true.
Parâmetros de caminho
idstringobrigatórioID ou e-mail do contato.
Erros
- 404
not_foundO recurso não existe ou é de outro time.
cURL
curl -X DELETE https://api.postal.so/contacts/019a1e00-1a2b-7c3d-8e4f-5a6b7c8d9e0f \
-H "Authorization: Bearer $POSTAL_API_KEY"
Node.js
const response = await fetch('https://api.postal.so/contacts/019a1e00-1a2b-7c3d-8e4f-5a6b7c8d9e0f', {
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/contacts/019a1e00-1a2b-7c3d-8e4f-5a6b7c8d9e0f",
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('contacts/019a1e00-1a2b-7c3d-8e4f-5a6b7c8d9e0f', [
'headers' => ['Authorization' => 'Bearer '.getenv('POSTAL_API_KEY')],
]);
$data = json_decode($response->getBody(), true);
{
"object": "contact",
"id": "019a1e00-1a2b-7c3d-8e4f-5a6b7c8d9e0f",
"deleted": true
}
#Segmentos de um contato
GET/contacts/{id}/segments
Os segmentos de que o contato faz parte. Aceita limit, after e before.
Parâmetros de caminho
idstringobrigatórioID ou e-mail do contato.
Erros
- 404
not_foundO recurso não existe ou é de outro time.
cURL
curl https://api.postal.so/contacts/019a1e00-1a2b-7c3d-8e4f-5a6b7c8d9e0f/segments \
-H "Authorization: Bearer $POSTAL_API_KEY"
Node.js
const response = await fetch('https://api.postal.so/contacts/019a1e00-1a2b-7c3d-8e4f-5a6b7c8d9e0f/segments', {
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/contacts/019a1e00-1a2b-7c3d-8e4f-5a6b7c8d9e0f/segments",
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('contacts/019a1e00-1a2b-7c3d-8e4f-5a6b7c8d9e0f/segments', [
'headers' => ['Authorization' => 'Bearer '.getenv('POSTAL_API_KEY')],
]);
$data = json_decode($response->getBody(), true);
{
"object": "list",
"has_more": false,
"data": [
{
"object": "segment",
"id": "019a1d90-3b4c-7d5e-9f6a-7b8c9d0e1f2a",
"name": "Clientes ativos",
"description": "Compraram nos últimos 90 dias.",
"created_at": "2026-10-20T10:00:00.000000Z",
"updated_at": "2026-10-20T10:00:00.000000Z"
}
]
}
#Adicionar a um segmento
POST/contacts/{id}/segments/{segment_id}
Coloca o contato no segmento. Se ele já estiver lá, nada muda.
Parâmetros de caminho
idstringobrigatórioID ou e-mail do contato.segment_idstringobrigatórioID do segmento.
Erros
- 404
not_foundO contato ou o segmento não existe.
cURL
curl -X POST https://api.postal.so/contacts/019a1e00-1a2b-7c3d-8e4f-5a6b7c8d9e0f/segments/019a1d90-3b4c-7d5e-9f6a-7b8c9d0e1f2a \
-H "Authorization: Bearer $POSTAL_API_KEY"
Node.js
const response = await fetch('https://api.postal.so/contacts/019a1e00-1a2b-7c3d-8e4f-5a6b7c8d9e0f/segments/019a1d90-3b4c-7d5e-9f6a-7b8c9d0e1f2a', {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.POSTAL_API_KEY}`,
},
});
const data = await response.json();
Python
import os
import requests
response = requests.post(
"https://api.postal.so/contacts/019a1e00-1a2b-7c3d-8e4f-5a6b7c8d9e0f/segments/019a1d90-3b4c-7d5e-9f6a-7b8c9d0e1f2a",
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->post('contacts/019a1e00-1a2b-7c3d-8e4f-5a6b7c8d9e0f/segments/019a1d90-3b4c-7d5e-9f6a-7b8c9d0e1f2a', [
'headers' => ['Authorization' => 'Bearer '.getenv('POSTAL_API_KEY')],
]);
$data = json_decode($response->getBody(), true);
{
"object": "contact_segment",
"contact_id": "019a1e00-1a2b-7c3d-8e4f-5a6b7c8d9e0f",
"segment_id": "019a1d90-3b4c-7d5e-9f6a-7b8c9d0e1f2a"
}
#Remover de um segmento
DELETE/contacts/{id}/segments/{segment_id}
Parâmetros de caminho
idstringobrigatórioID ou e-mail do contato.segment_idstringobrigatórioID do segmento.
Erros
- 404
not_foundO contato ou o segmento não existe.
cURL
curl -X DELETE https://api.postal.so/contacts/019a1e00-1a2b-7c3d-8e4f-5a6b7c8d9e0f/segments/019a1d90-3b4c-7d5e-9f6a-7b8c9d0e1f2a \
-H "Authorization: Bearer $POSTAL_API_KEY"
Node.js
const response = await fetch('https://api.postal.so/contacts/019a1e00-1a2b-7c3d-8e4f-5a6b7c8d9e0f/segments/019a1d90-3b4c-7d5e-9f6a-7b8c9d0e1f2a', {
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/contacts/019a1e00-1a2b-7c3d-8e4f-5a6b7c8d9e0f/segments/019a1d90-3b4c-7d5e-9f6a-7b8c9d0e1f2a",
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('contacts/019a1e00-1a2b-7c3d-8e4f-5a6b7c8d9e0f/segments/019a1d90-3b4c-7d5e-9f6a-7b8c9d0e1f2a', [
'headers' => ['Authorization' => 'Bearer '.getenv('POSTAL_API_KEY')],
]);
$data = json_decode($response->getBody(), true);
{
"object": "contact_segment",
"contact_id": "019a1e00-1a2b-7c3d-8e4f-5a6b7c8d9e0f",
"segment_id": "019a1d90-3b4c-7d5e-9f6a-7b8c9d0e1f2a",
"deleted": true
}
#Tópicos de um contato
GET/contacts/{id}/topics
Todos os tópicos do time, em ordem alfabética, com a inscrição do contato: a escolha dele ou, se ele não escolheu, o padrão do tópico.
Parâmetros de caminho
idstringobrigatórioID ou e-mail do contato.
Erros
- 404
not_foundO recurso não existe ou é de outro time.
cURL
curl https://api.postal.so/contacts/019a1e00-1a2b-7c3d-8e4f-5a6b7c8d9e0f/topics \
-H "Authorization: Bearer $POSTAL_API_KEY"
Node.js
const response = await fetch('https://api.postal.so/contacts/019a1e00-1a2b-7c3d-8e4f-5a6b7c8d9e0f/topics', {
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/contacts/019a1e00-1a2b-7c3d-8e4f-5a6b7c8d9e0f/topics",
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('contacts/019a1e00-1a2b-7c3d-8e4f-5a6b7c8d9e0f/topics', [
'headers' => ['Authorization' => 'Bearer '.getenv('POSTAL_API_KEY')],
]);
$data = json_decode($response->getBody(), true);
{
"object": "list",
"has_more": false,
"data": [
{
"id": "019a1d95-4c5d-7e6f-8a7b-8c9d0e1f2a3b",
"name": "Novidades",
"description": "Lançamentos e promoções, uma vez por semana.",
"visibility": "public",
"default_subscription": "opt_in",
"subscription": "opt_in"
},
{
"id": "019a1d95-4c5e-7f70-9b8c-9d0e1f2a3b4c",
"name": "Pesquisas",
"description": null,
"visibility": "private",
"default_subscription": "opt_out",
"subscription": "opt_out"
}
]
}
#Mudar a inscrição em tópicos
PATCH/contacts/{id}/topics
Registra a escolha do contato em um ou mais tópicos. Os tópicos não enviados ficam como estão.
Parâmetros de caminho
idstringobrigatórioID ou e-mail do contato.
Corpo da requisição
topicsobject[]obrigatórioDe 1 a 100 escolhas.idstringobrigatórioID do tópico.subscriptionstringobrigatórioopt_inouopt_out.
Erros
- 422
validation_errorAlgum campo é inválido. A mensagem diz qual. - 404
not_foundO contato ou algum tópico não existe.
cURL
curl -X PATCH https://api.postal.so/contacts/019a1e00-1a2b-7c3d-8e4f-5a6b7c8d9e0f/topics \
-H "Authorization: Bearer $POSTAL_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"topics": [
{
"id": "019a1d95-4c5d-7e6f-8a7b-8c9d0e1f2a3b",
"subscription": "opt_out"
}
]
}'
Node.js
const response = await fetch('https://api.postal.so/contacts/019a1e00-1a2b-7c3d-8e4f-5a6b7c8d9e0f/topics', {
method: 'PATCH',
headers: {
Authorization: `Bearer ${process.env.POSTAL_API_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
topics: [
{
id: '019a1d95-4c5d-7e6f-8a7b-8c9d0e1f2a3b',
subscription: 'opt_out',
},
],
}),
});
const data = await response.json();
Python
import os
import requests
response = requests.patch(
"https://api.postal.so/contacts/019a1e00-1a2b-7c3d-8e4f-5a6b7c8d9e0f/topics",
headers={"Authorization": f"Bearer {os.environ['POSTAL_API_KEY']}"},
json={
"topics": [
{
"id": "019a1d95-4c5d-7e6f-8a7b-8c9d0e1f2a3b",
"subscription": "opt_out",
},
],
},
timeout=10,
)
data = response.json()
PHP
$client = new \GuzzleHttp\Client(['base_uri' => 'https://api.postal.so']);
$response = $client->patch('contacts/019a1e00-1a2b-7c3d-8e4f-5a6b7c8d9e0f/topics', [
'headers' => ['Authorization' => 'Bearer '.getenv('POSTAL_API_KEY')],
'json' => [
'topics' => [
[
'id' => '019a1d95-4c5d-7e6f-8a7b-8c9d0e1f2a3b',
'subscription' => 'opt_out',
],
],
],
]);
$data = json_decode($response->getBody(), true);
{
"object": "contact",
"id": "019a1e00-1a2b-7c3d-8e4f-5a6b7c8d9e0f"
}