Segmentos
Listas de contatos para direcionar campanhas.
Segmentos são listas de contatos, para direcionar campanhas. Um contato pode estar em vários segmentos. Adicione e remova contatos pelos endpoints de contatos, na importação ou com formulários. O plano Grátis permite até 3 segmentos; os planos pagos, quantos você quiser.
Há dois tipos, no campo type: static (padrão), uma lista que você monta contato a contato, e dynamic, definida por filtros e atualizada sozinha conforme os contatos mudam. Só os segmentos estáticos aceitam contatos à mão (pelos endpoints de contatos, pela importação e pelos formulários); tentar isso num dinâmico dá 422. O tipo não muda depois de criado, mas um dinâmico pode ser convertido em estático.
#Segmentos dinâmicos
Um segmento dinâmico guarda quem combina com os filtros agora. Ao criar ou mudar os filtros, os membros são calculados em segundo plano (sem iniciar automações); depois, a lista se atualiza quando um contato muda e, para filtros que dependem do tempo ("nos últimos 30 dias"), a cada hora. Quando um contato entra ou sai, a Postal dispara os webhooks contact.segment_entered e contact.segment_exited e as automações com o gatilho de segmento adicionado ou removido. O contacts_count de um dinâmico é o número de contatos que combinam, guardado por até um minuto. Criar um segmento dinâmico exige que o plano o inclua (403 plan_limit_reached se não incluir); os já criados continuam funcionando.
Os filtros são grupos de condições. Cada grupo combina as suas condições com match (all: todas, any: qualquer uma) e o segmento combina os grupos do mesmo jeito. Pode haver até 10 grupos e 20 condições no total. Comparações de texto não diferenciam maiúsculas, e os operadores negativos (is_not, not_contains, has_not…) também pegam quem não tem valor. A Postal confere tudo (propriedades, etiquetas, tópicos, campanhas…) e devolve os filtros na forma canônica.
{
"match": "all",
"groups": [
{
"match": "all",
"conditions": [
{ "field": "tag", "operator": "has", "tag_id": "019a1d70-6a7b-7c8d-9e0f-1a2b3c4d5e6f" },
{ "field": "created_at", "operator": "in_last_days", "value": 90 }
]
},
{
"match": "any",
"conditions": [
{ "field": "property", "key": "plan", "operator": "is", "value": "pro" },
{ "field": "event", "operator": "performed", "event": "order.paid", "within_days": 30, "min_count": 2 }
]
}
]
}
Campo (field) | Operadores | Valor e extras |
|---|---|---|
email, first_name, last_name | is, is_not, contains, not_contains, starts_with, is_empty, is_set | value: texto de até 1.000 caracteres (não vale para is_empty e is_set). |
created_at | before, after, between, in_last_days, more_than_days_ago | value: data AAAA-MM-DD; between leva ["AAAA-MM-DD", "AAAA-MM-DD"]; os de dias levam um número de 1 a 3.650. |
unsubscribed | is | value: true ou false. |
email_status | is, is_not | value: lista com valid, invalid, risky e unverified (resultado da validação de e-mails). |
consent | is, is_not | value: confirmed ou unconfirmed (se o contato pode receber marketing; veja contatos guardados dos e-mails transacionais). |
origin | is, is_not | value: transactional (os contatos guardados dos e-mails transacionais). |
property | Depende do tipo da propriedade: texto como acima; número: is, is_not, greater_than, less_than, between, is_empty, is_set; booleano: is, is_empty, is_set; data: como created_at, mais is_empty e is_set. | key: a chave da propriedade; between de números leva [mínimo, máximo]; is: false também pega quem não tem valor. |
tag | has, has_not | tag_id: ID de uma etiqueta. |
topic | subscribed, unsubscribed | topic_id: ID de um tópico. |
segment | in, not_in | segment_id: ID de um segmento estático. |
email_activity | performed, not_performed | activity: received, opened, clicked, replied, bounced ou complained. Opcionais: um entre broadcast_id, template_id e automation_id; within_days (1 a 3.650; vazio: desde sempre); min_count (1 a 1.000; padrão 1). |
event | performed, not_performed | event: o nome do evento. Opcionais: within_days, min_count e property ({"key": "plan", "value": "pro"}: só contam eventos com esse valor). |
#Listar segmentos
GET/segments
Cada segmento vem com contacts_count. Para ver os contatos, use GET /contacts?segment_id=….
Parâmetros de consulta
limitintegerDe 1 a 100. Padrão: 20.afterstringCursor da próxima página.beforestringCursor da página anterior.searchstringBusca no nome.typestringstaticoudynamic.
cURL
curl https://api.postal.so/segments \
-H "Authorization: Bearer $POSTAL_API_KEY"
Node.js
const response = await fetch('https://api.postal.so/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/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('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",
"paused_by_plan": false,
"name": "Clientes ativos",
"description": "Compraram nos últimos 90 dias.",
"type": "static",
"contacts_count": 1284,
"created_at": "2026-10-20T10:00:00.000000Z",
"updated_at": "2026-10-20T10:00:00.000000Z"
},
{
"object": "segment",
"id": "019a1d90-3b4c-7d5e-9f6a-7b8c9d0e1f2a",
"paused_by_plan": false,
"name": "Clientes VIP recentes",
"description": null,
"type": "dynamic",
"contacts_count": 342,
"created_at": "2026-10-20T10:00:00.000000Z",
"updated_at": "2026-10-20T10:00:00.000000Z",
"filters": {
"match": "all",
"groups": [
{
"match": "all",
"conditions": [
{
"field": "tag",
"operator": "has",
"tag_id": "019a1d70-6a7b-7c8d-9e0f-1a2b3c4d5e6f"
},
{
"field": "created_at",
"operator": "in_last_days",
"value": 90
}
]
}
]
}
}
]
}
#Criar um segmento
POST/segments
Corpo da requisição
namestringobrigatórioNome, até 255 caracteres.descriptionstring | nullDescrição, até 500 caracteres.typestringstatic(padrão) oudynamic.filtersobjectOs filtros. Obrigatórios num segmento dinâmico; um estático não aceita filtros.
Erros
- 422
validation_errorAlgum campo é inválido. A mensagem diz qual. - 403
plan_limit_reachedO plano não permite mais segmentos, ou não inclui segmentos dinâmicos. - 404
not_foundUma etiqueta, tópico, segmento, campanha, template ou automação citados nos filtros não existe.
cURL
curl -X POST https://api.postal.so/segments \
-H "Authorization: Bearer $POSTAL_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"name": "Clientes ativos",
"description": "Compraram nos últimos 90 dias."
}'
Node.js
const response = await fetch('https://api.postal.so/segments', {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.POSTAL_API_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
name: 'Clientes ativos',
description: 'Compraram nos últimos 90 dias.',
}),
});
const data = await response.json();
Python
import os
import requests
response = requests.post(
"https://api.postal.so/segments",
headers={"Authorization": f"Bearer {os.environ['POSTAL_API_KEY']}"},
json={
"name": "Clientes ativos",
"description": "Compraram nos últimos 90 dias.",
},
timeout=10,
)
data = response.json()
PHP
$client = new \GuzzleHttp\Client(['base_uri' => 'https://api.postal.so']);
$response = $client->post('segments', [
'headers' => ['Authorization' => 'Bearer '.getenv('POSTAL_API_KEY')],
'json' => [
'name' => 'Clientes ativos',
'description' => 'Compraram nos últimos 90 dias.',
],
]);
$data = json_decode($response->getBody(), true);
{
"object": "segment",
"id": "019a1d90-3b4c-7d5e-9f6a-7b8c9d0e1f2a",
"name": "Clientes ativos",
"type": "static"
}
#Criar um segmento dinâmico
POST/segments
O mesmo endpoint, com type: "dynamic" e filters. Use POST /segments/preview antes para ver quantos contatos combinam.
Erros
- 422
validation_errorAlgum campo é inválido. A mensagem diz qual. - 403
plan_limit_reachedO plano não permite mais segmentos, ou não inclui segmentos dinâmicos.
cURL
curl -X POST https://api.postal.so/segments \
-H "Authorization: Bearer $POSTAL_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"name": "Clientes VIP recentes",
"type": "dynamic",
"filters": {
"match": "all",
"groups": [
{
"match": "all",
"conditions": [
{
"field": "tag",
"operator": "has",
"tag_id": "019a1d70-6a7b-7c8d-9e0f-1a2b3c4d5e6f"
},
{
"field": "created_at",
"operator": "in_last_days",
"value": 90
}
]
}
]
}
}'
Node.js
const response = await fetch('https://api.postal.so/segments', {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.POSTAL_API_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
name: 'Clientes VIP recentes',
type: 'dynamic',
filters: {
match: 'all',
groups: [
{
match: 'all',
conditions: [
{
field: 'tag',
operator: 'has',
tag_id: '019a1d70-6a7b-7c8d-9e0f-1a2b3c4d5e6f',
},
{
field: 'created_at',
operator: 'in_last_days',
value: 90,
},
],
},
],
},
}),
});
const data = await response.json();
Python
import os
import requests
response = requests.post(
"https://api.postal.so/segments",
headers={"Authorization": f"Bearer {os.environ['POSTAL_API_KEY']}"},
json={
"name": "Clientes VIP recentes",
"type": "dynamic",
"filters": {
"match": "all",
"groups": [
{
"match": "all",
"conditions": [
{
"field": "tag",
"operator": "has",
"tag_id": "019a1d70-6a7b-7c8d-9e0f-1a2b3c4d5e6f",
},
{
"field": "created_at",
"operator": "in_last_days",
"value": 90,
},
],
},
],
},
},
timeout=10,
)
data = response.json()
PHP
$client = new \GuzzleHttp\Client(['base_uri' => 'https://api.postal.so']);
$response = $client->post('segments', [
'headers' => ['Authorization' => 'Bearer '.getenv('POSTAL_API_KEY')],
'json' => [
'name' => 'Clientes VIP recentes',
'type' => 'dynamic',
'filters' => [
'match' => 'all',
'groups' => [
[
'match' => 'all',
'conditions' => [
[
'field' => 'tag',
'operator' => 'has',
'tag_id' => '019a1d70-6a7b-7c8d-9e0f-1a2b3c4d5e6f',
],
[
'field' => 'created_at',
'operator' => 'in_last_days',
'value' => 90,
],
],
],
],
],
],
]);
$data = json_decode($response->getBody(), true);
{
"object": "segment",
"id": "019a1d90-3b4c-7d5e-9f6a-7b8c9d0e1f2a",
"name": "Clientes VIP recentes",
"type": "dynamic"
}
#Visualizar os filtros de um segmento
POST/segments/preview
Confere os filtros e diz quantos contatos combinam agora, sem criar nada. Devolve os filtros na forma canônica e até 10 contatos de exemplo (os mais recentes).
Corpo da requisição
filtersobjectobrigatórioOs filtros.
Erros
- 422
validation_errorAlgum campo é inválido. A mensagem diz qual. - 404
not_foundAlgum item citado nos filtros não existe.
cURL
curl -X POST https://api.postal.so/segments/preview \
-H "Authorization: Bearer $POSTAL_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"filters": {
"match": "all",
"groups": [
{
"match": "all",
"conditions": [
{
"field": "tag",
"operator": "has",
"tag_id": "019a1d70-6a7b-7c8d-9e0f-1a2b3c4d5e6f"
},
{
"field": "created_at",
"operator": "in_last_days",
"value": 90
}
]
}
]
}
}'
Node.js
const response = await fetch('https://api.postal.so/segments/preview', {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.POSTAL_API_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
filters: {
match: 'all',
groups: [
{
match: 'all',
conditions: [
{
field: 'tag',
operator: 'has',
tag_id: '019a1d70-6a7b-7c8d-9e0f-1a2b3c4d5e6f',
},
{
field: 'created_at',
operator: 'in_last_days',
value: 90,
},
],
},
],
},
}),
});
const data = await response.json();
Python
import os
import requests
response = requests.post(
"https://api.postal.so/segments/preview",
headers={"Authorization": f"Bearer {os.environ['POSTAL_API_KEY']}"},
json={
"filters": {
"match": "all",
"groups": [
{
"match": "all",
"conditions": [
{
"field": "tag",
"operator": "has",
"tag_id": "019a1d70-6a7b-7c8d-9e0f-1a2b3c4d5e6f",
},
{
"field": "created_at",
"operator": "in_last_days",
"value": 90,
},
],
},
],
},
},
timeout=10,
)
data = response.json()
PHP
$client = new \GuzzleHttp\Client(['base_uri' => 'https://api.postal.so']);
$response = $client->post('segments/preview', [
'headers' => ['Authorization' => 'Bearer '.getenv('POSTAL_API_KEY')],
'json' => [
'filters' => [
'match' => 'all',
'groups' => [
[
'match' => 'all',
'conditions' => [
[
'field' => 'tag',
'operator' => 'has',
'tag_id' => '019a1d70-6a7b-7c8d-9e0f-1a2b3c4d5e6f',
],
[
'field' => 'created_at',
'operator' => 'in_last_days',
'value' => 90,
],
],
],
],
],
],
]);
$data = json_decode($response->getBody(), true);
{
"object": "segment_preview",
"filters": {
"match": "all",
"groups": [
{
"match": "all",
"conditions": [
{
"field": "tag",
"operator": "has",
"tag_id": "019a1d70-6a7b-7c8d-9e0f-1a2b3c4d5e6f"
},
{
"field": "created_at",
"operator": "in_last_days",
"value": 90
}
]
}
]
},
"contacts_count": 342,
"sample": [
{
"object": "contact",
"id": "019a1e00-1a2b-7c3d-8e4f-5a6b7c8d9e0f",
"email": "[email protected]",
"external_id": "user_42",
"user_id": "user_42",
"first_name": "Ana",
"last_name": "Souza",
"unsubscribed": false,
"marketing_consent": "confirmed",
"origin": null,
"consent_confirmed_at": null,
"email_status": "valid",
"email_verified_at": "2026-10-20T09:30:00.000000Z",
"properties": {
"plan": "pro",
"city": "Recife",
"customer_since": "2024-03-18"
},
"last_change_source": "api",
"created_at": "2026-10-24T14:00:00.000000Z",
"updated_at": "2026-10-24T14:00:00.000000Z",
"tags": [
{
"id": "019a1d70-6a7b-7c8d-9e0f-1a2b3c4d5e6f",
"name": "Cliente VIP",
"color": "purple"
}
]
}
]
}
#Obter um segmento
GET/segments/{id}
Um segmento dinâmico traz também os filters. paused_by_plan é true quando o plano deixou de comportar o segmento: ele continua listado e pode ser lido ou excluído, mas não pode ser alterado (403 paused_by_plan).
Parâmetros de caminho
idstringobrigatórioID do segmento.
Erros
- 404
not_foundO recurso não existe ou é de outro time.
cURL
curl https://api.postal.so/segments/019a1d90-3b4c-7d5e-9f6a-7b8c9d0e1f2a \
-H "Authorization: Bearer $POSTAL_API_KEY"
Node.js
const response = await fetch('https://api.postal.so/segments/019a1d90-3b4c-7d5e-9f6a-7b8c9d0e1f2a', {
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/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->get('segments/019a1d90-3b4c-7d5e-9f6a-7b8c9d0e1f2a', [
'headers' => ['Authorization' => 'Bearer '.getenv('POSTAL_API_KEY')],
]);
$data = json_decode($response->getBody(), true);
{
"object": "segment",
"id": "019a1d90-3b4c-7d5e-9f6a-7b8c9d0e1f2a",
"paused_by_plan": false,
"name": "Clientes ativos",
"description": "Compraram nos últimos 90 dias.",
"type": "static",
"contacts_count": 1284,
"created_at": "2026-10-20T10:00:00.000000Z",
"updated_at": "2026-10-20T10:00:00.000000Z"
}
#Atualizar um segmento
PATCH/segments/{id}
Mudar os filters de um segmento dinâmico recalcula os membros em segundo plano. O type não muda.
Parâmetros de caminho
idstringobrigatórioID do segmento.
Corpo da requisição
namestringNovo nome.descriptionstring | nullNova descrição.filtersobjectNovos filtros (só em segmentos dinâmicos).
Erros
- 422
validation_errorAlgum campo é inválido. A mensagem diz qual. - 403
paused_by_planO segmento está pausado porque passa do limite do plano. - 404
not_foundO recurso não existe ou é de outro time.
cURL
curl -X PATCH https://api.postal.so/segments/019a1d90-3b4c-7d5e-9f6a-7b8c9d0e1f2a \
-H "Authorization: Bearer $POSTAL_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"name": "Clientes ativos (90 dias)"
}'
Node.js
const response = await fetch('https://api.postal.so/segments/019a1d90-3b4c-7d5e-9f6a-7b8c9d0e1f2a', {
method: 'PATCH',
headers: {
Authorization: `Bearer ${process.env.POSTAL_API_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
name: 'Clientes ativos (90 dias)',
}),
});
const data = await response.json();
Python
import os
import requests
response = requests.patch(
"https://api.postal.so/segments/019a1d90-3b4c-7d5e-9f6a-7b8c9d0e1f2a",
headers={"Authorization": f"Bearer {os.environ['POSTAL_API_KEY']}"},
json={
"name": "Clientes ativos (90 dias)",
},
timeout=10,
)
data = response.json()
PHP
$client = new \GuzzleHttp\Client(['base_uri' => 'https://api.postal.so']);
$response = $client->patch('segments/019a1d90-3b4c-7d5e-9f6a-7b8c9d0e1f2a', [
'headers' => ['Authorization' => 'Bearer '.getenv('POSTAL_API_KEY')],
'json' => [
'name' => 'Clientes ativos (90 dias)',
],
]);
$data = json_decode($response->getBody(), true);
{
"object": "segment",
"id": "019a1d90-3b4c-7d5e-9f6a-7b8c9d0e1f2a"
}
#Converter em estático
POST/segments/{id}/convert-to-static
Os contatos que combinam agora viram a lista de um segmento estático, que passa a guardá-los aconteça o que acontecer. Campanhas, automações e formulários que usam o segmento continuam apontando para ele.
Parâmetros de caminho
idstringobrigatórioID do segmento.
Erros
- 409
invalid_segment_typeO segmento já é estático. - 403
paused_by_planO segmento está pausado pelo plano. - 404
not_foundO recurso não existe ou é de outro time.
cURL
curl -X POST https://api.postal.so/segments/019a1d90-3b4c-7d5e-9f6a-7b8c9d0e1f2a/convert-to-static \
-H "Authorization: Bearer $POSTAL_API_KEY"
Node.js
const response = await fetch('https://api.postal.so/segments/019a1d90-3b4c-7d5e-9f6a-7b8c9d0e1f2a/convert-to-static', {
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/segments/019a1d90-3b4c-7d5e-9f6a-7b8c9d0e1f2a/convert-to-static",
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('segments/019a1d90-3b4c-7d5e-9f6a-7b8c9d0e1f2a/convert-to-static', [
'headers' => ['Authorization' => 'Bearer '.getenv('POSTAL_API_KEY')],
]);
$data = json_decode($response->getBody(), true);
{
"object": "segment",
"id": "019a1d90-3b4c-7d5e-9f6a-7b8c9d0e1f2a",
"paused_by_plan": false,
"name": "Clientes VIP recentes",
"description": "Compraram nos últimos 90 dias.",
"type": "static",
"contacts_count": 342,
"created_at": "2026-10-20T10:00:00.000000Z",
"updated_at": "2026-10-20T10:00:00.000000Z"
}
#Excluir um segmento
DELETE/segments/{id}
Apaga só a lista: os contatos continuam na audiência. Um segmento que recebe os contatos de um formulário não pode ser excluído; troque o segmento do formulário antes.
Um segmento estático que algum segmento dinâmico usa nos filtros também não pode ser excluído: mude os filtros antes.
Parâmetros de caminho
idstringobrigatórioID do segmento.
Erros
- 409
segment_in_useAlgum formulário envia contatos para este segmento, ou algum segmento dinâmico confere quem está nele. A mensagem diz quais. - 404
not_foundO recurso não existe ou é de outro time.
cURL
curl -X DELETE https://api.postal.so/segments/019a1d90-3b4c-7d5e-9f6a-7b8c9d0e1f2a \
-H "Authorization: Bearer $POSTAL_API_KEY"
Node.js
const response = await fetch('https://api.postal.so/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/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('segments/019a1d90-3b4c-7d5e-9f6a-7b8c9d0e1f2a', [
'headers' => ['Authorization' => 'Bearer '.getenv('POSTAL_API_KEY')],
]);
$data = json_decode($response->getBody(), true);
{
"object": "segment",
"id": "019a1d90-3b4c-7d5e-9f6a-7b8c9d0e1f2a",
"deleted": true
}
#Unificar segmentos
Junta vários segmentos estáticos em um só. O fluxo tem duas etapas: a prévia responde na hora, com as contagens, os contatos que parecem repetidos e tudo o que aponta para os segmentos de origem; a unificação exige uma escolha para cada item ligado (resolutions) e roda em segundo plano, com o andamento em GET /segments/merges/{id}. Só uma unificação roda por vez no time. Segmentos dinâmicos não entram: converta-os em estáticos antes.
Item ligado (kind) | Escolhas (resolutions) |
|---|---|
broadcast (rascunho) | retarget, keep |
broadcast (agendada) | retarget, keep, cancel |
broadcast (na fila, enviando ou pausada) | Nenhuma: ela mantém o segmento e impede excluir a origem até terminar. |
automation | retarget, keep e, se estiver ativa, pause |
form, segment (dinâmico) | retarget, keep |
contact_import | Nenhuma: uma importação em andamento impede excluir a origem. |
retarget aponta o item para o segmento de destino, keep deixa como está (só se as origens não forem excluídas), cancel cancela o agendamento e pause pausa a automação para revisão. A chave de cada item é kind:id, como broadcast:019a…; a prévia lista os itens em impact, com as choices e o default de cada um.
#Visualizar uma unificação
POST/segments/merge/preview
Não altera nada. Devolve, em counts, as inscrições (memberships), os contatos únicos, as inscrições repetidas e quantos contatos são novos no destino; em possible_duplicates, grupos de contatos que parecem a mesma caixa de e-mail com endereços diferentes; em impact, os itens ligados (veja a tabela acima); em blocking, as chaves dos itens que impedem excluir as origens; e em warnings, avisos sobre a exclusão.
Corpo da requisição
source_idsstring[]obrigatórioDe 2 a 20 IDs de segmentos estáticos, sem repetir. O destino pode ser um deles.targetobjectobrigatórioQuem recebe os contatos:{"segment_id": "…"}de um segmento existente, ou{"name": "…"}para criar um novo (até 255 caracteres).delete_sourcesbooleanExclui os segmentos de origem no fim. Padrão:false.
Erros
- 422
validation_errorAlgum campo é inválido. A mensagem diz qual. - 404
not_foundAlgum segmento não existe.
cURL
curl -X POST https://api.postal.so/segments/merge/preview \
-H "Authorization: Bearer $POSTAL_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"source_ids": [
"019a1d90-3b4c-7d5e-9f6a-7b8c9d0e1f2a",
"019a1d91-4c5d-7e6f-8a7b-8c9d0e1f2a3b"
],
"target": {
"name": "Clientes (todos)"
},
"delete_sources": false
}'
Node.js
const response = await fetch('https://api.postal.so/segments/merge/preview', {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.POSTAL_API_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
source_ids: [
'019a1d90-3b4c-7d5e-9f6a-7b8c9d0e1f2a',
'019a1d91-4c5d-7e6f-8a7b-8c9d0e1f2a3b',
],
target: {
name: 'Clientes (todos)',
},
delete_sources: false,
}),
});
const data = await response.json();
Python
import os
import requests
response = requests.post(
"https://api.postal.so/segments/merge/preview",
headers={"Authorization": f"Bearer {os.environ['POSTAL_API_KEY']}"},
json={
"source_ids": [
"019a1d90-3b4c-7d5e-9f6a-7b8c9d0e1f2a",
"019a1d91-4c5d-7e6f-8a7b-8c9d0e1f2a3b",
],
"target": {
"name": "Clientes (todos)",
},
"delete_sources": False,
},
timeout=10,
)
data = response.json()
PHP
$client = new \GuzzleHttp\Client(['base_uri' => 'https://api.postal.so']);
$response = $client->post('segments/merge/preview', [
'headers' => ['Authorization' => 'Bearer '.getenv('POSTAL_API_KEY')],
'json' => [
'source_ids' => [
'019a1d90-3b4c-7d5e-9f6a-7b8c9d0e1f2a',
'019a1d91-4c5d-7e6f-8a7b-8c9d0e1f2a3b',
],
'target' => [
'name' => 'Clientes (todos)',
],
'delete_sources' => false,
],
]);
$data = json_decode($response->getBody(), true);
{
"object": "segment_merge_preview",
"sources": [
{
"id": "019a1d90-3b4c-7d5e-9f6a-7b8c9d0e1f2a",
"name": "Clientes ativos",
"contacts_count": 1284,
"past_broadcasts_count": 2
},
{
"id": "019a1d91-4c5d-7e6f-8a7b-8c9d0e1f2a3b",
"name": "Compradores",
"contacts_count": 1900,
"past_broadcasts_count": 2
}
],
"target": {
"id": null,
"name": "Clientes (todos)",
"exists": false,
"contacts_count": 0
},
"delete_sources": false,
"counts": {
"memberships": 3184,
"unique_contacts": 3100,
"duplicate_memberships": 84,
"new_to_target": 3100,
"duplicate_groups": 1,
"possible_duplicate_contacts": 1,
"unique_contacts_after_merging_duplicates": 3099
},
"possible_duplicates": {
"total_groups": 1,
"total_contacts": 1,
"has_more": false,
"groups": [
{
"mailbox": "[email protected]",
"size": 2,
"contacts": [
{
"id": "019a1e00-1a2b-7c3d-8e4f-5a6b7c8d9e0f",
"email": "[email protected]",
"first_name": "Ana",
"last_name": "Souza",
"unsubscribed": false,
"engaged_emails": 14,
"created_at": "2026-10-24T14:00:00.000000Z",
"keep": true
},
{
"id": "019a1e00-3c4d-7e5f-8a60-7c8d9e0f1a2b",
"email": "[email protected]",
"first_name": "Ana",
"last_name": "Souza",
"unsubscribed": false,
"engaged_emails": 0,
"created_at": "2026-10-25T09:00:00.000000Z",
"keep": false
}
]
}
]
},
"impact": [
{
"key": "broadcast:019a1f00-6e7f-7a80-9b90-1d2e3f4a5b6c",
"kind": "broadcast",
"id": "019a1f00-6e7f-7a80-9b90-1d2e3f4a5b6c",
"name": "Promoção de outubro",
"status": "draft",
"segment_ids": ["019a1d90-3b4c-7d5e-9f6a-7b8c9d0e1f2a"],
"references": null,
"choices": ["retarget", "keep"],
"default": "retarget",
"blocks_deletion": false
}
],
"blocking": [],
"warnings": []
}
#Unificar segmentos
POST/segments/merge
Começa a unificação em segundo plano e devolve a unificação (202). Aceita o cabeçalho Idempotency-Key. Os itens ligados que a prévia listou precisam ter uma escolha em resolutions; os que não têm opção (como uma campanha em envio) impedem excluir as origens.
Corpo da requisição
source_idsstring[]obrigatórioDe 2 a 20 IDs de segmentos estáticos, sem repetir. O destino pode ser um deles.targetobjectobrigatórioQuem recebe os contatos:{"segment_id": "…"}de um segmento existente, ou{"name": "…"}para criar um novo (até 255 caracteres).delete_sourcesbooleanExclui os segmentos de origem no fim. Padrão:false.merge_duplicate_contactsbooleanJunta também os contatos que parecem repetidos (mesma caixa de e-mail). Padrão:false.resolutionsobjectA escolha de cada item ligado:{"broadcast:019a…": "retarget"}, comretarget,keep,canceloupause.
Erros
- 422
validation_errorFaltou escolher o que fazer com algum item ligado (errors.missing), a escolha não existe para o item (errors.invalid), algum segmento é dinâmico ou os dados são inválidos. - 409
merge_in_progressOutra unificação do time ainda está rodando. - 409
segment_in_useHá itens que impedem excluir as origens (errors.blocking): espere terminarem ou mantenha os segmentos. - 403
plan_limit_reachedCom destino novo e sem excluir as origens, o time passaria do limite de segmentos. - 404
not_foundAlgum segmento não existe.
cURL
curl -X POST https://api.postal.so/segments/merge \
-H "Authorization: Bearer $POSTAL_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"source_ids": [
"019a1d90-3b4c-7d5e-9f6a-7b8c9d0e1f2a",
"019a1d91-4c5d-7e6f-8a7b-8c9d0e1f2a3b"
],
"target": {
"name": "Clientes (todos)"
},
"delete_sources": false,
"resolutions": {
"broadcast:019a1f00-6e7f-7a80-9b90-1d2e3f4a5b6c": "retarget"
}
}'
Node.js
const response = await fetch('https://api.postal.so/segments/merge', {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.POSTAL_API_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
source_ids: [
'019a1d90-3b4c-7d5e-9f6a-7b8c9d0e1f2a',
'019a1d91-4c5d-7e6f-8a7b-8c9d0e1f2a3b',
],
target: {
name: 'Clientes (todos)',
},
delete_sources: false,
resolutions: {
'broadcast:019a1f00-6e7f-7a80-9b90-1d2e3f4a5b6c': 'retarget',
},
}),
});
const data = await response.json();
Python
import os
import requests
response = requests.post(
"https://api.postal.so/segments/merge",
headers={"Authorization": f"Bearer {os.environ['POSTAL_API_KEY']}"},
json={
"source_ids": [
"019a1d90-3b4c-7d5e-9f6a-7b8c9d0e1f2a",
"019a1d91-4c5d-7e6f-8a7b-8c9d0e1f2a3b",
],
"target": {
"name": "Clientes (todos)",
},
"delete_sources": False,
"resolutions": {
"broadcast:019a1f00-6e7f-7a80-9b90-1d2e3f4a5b6c": "retarget",
},
},
timeout=10,
)
data = response.json()
PHP
$client = new \GuzzleHttp\Client(['base_uri' => 'https://api.postal.so']);
$response = $client->post('segments/merge', [
'headers' => ['Authorization' => 'Bearer '.getenv('POSTAL_API_KEY')],
'json' => [
'source_ids' => [
'019a1d90-3b4c-7d5e-9f6a-7b8c9d0e1f2a',
'019a1d91-4c5d-7e6f-8a7b-8c9d0e1f2a3b',
],
'target' => [
'name' => 'Clientes (todos)',
],
'delete_sources' => false,
'resolutions' => [
'broadcast:019a1f00-6e7f-7a80-9b90-1d2e3f4a5b6c' => 'retarget',
],
],
]);
$data = json_decode($response->getBody(), true);
{
"object": "segment_merge",
"id": "019a1e40-abb2-7ec4-9f30-5b6c7d8e9fa0",
"status": "pending",
"stage": null,
"progress": {
"processed": 0,
"total": 0
},
"sources": [
{
"id": "019a1d90-3b4c-7d5e-9f6a-7b8c9d0e1f2a",
"name": "Clientes ativos",
"contacts_count": 1284
},
{
"id": "019a1d91-4c5d-7e6f-8a7b-8c9d0e1f2a3b",
"name": "Compradores",
"contacts_count": 1900
}
],
"target": {
"id": "019a1d92-5d6e-7f70-9b8c-9d0e1f2a3b4c",
"name": "Clientes (todos)"
},
"delete_sources": false,
"merge_duplicate_contacts": false,
"counts": {
"memberships": 3184,
"unique_contacts": 3100,
"duplicate_memberships": 84,
"contacts_added": 0,
"contacts_merged": 0,
"items_updated": 0,
"sources_deleted": 0
},
"summary": {
"merged_contacts": [],
"merged_contacts_total": 0,
"items": [],
"kept_sources": []
},
"error": null,
"user_id": null,
"created_at": "2026-10-24T16:00:00.000000Z",
"started_at": null,
"completed_at": null
}
#Obter uma unificação
GET/segments/merges/{id}
status: pending, processing, completed ou failed (com error). progress mostra os itens tratados no passo atual (stage), e counts e summary resumem o que foi feito.
Parâmetros de caminho
idstringobrigatórioID da unificação.
Erros
- 404
not_foundO recurso não existe ou é de outro time.
cURL
curl https://api.postal.so/segments/merges/019a1e40-abb2-7ec4-9f30-5b6c7d8e9fa0 \
-H "Authorization: Bearer $POSTAL_API_KEY"
Node.js
const response = await fetch('https://api.postal.so/segments/merges/019a1e40-abb2-7ec4-9f30-5b6c7d8e9fa0', {
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/segments/merges/019a1e40-abb2-7ec4-9f30-5b6c7d8e9fa0",
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('segments/merges/019a1e40-abb2-7ec4-9f30-5b6c7d8e9fa0', [
'headers' => ['Authorization' => 'Bearer '.getenv('POSTAL_API_KEY')],
]);
$data = json_decode($response->getBody(), true);
{
"object": "segment_merge",
"id": "019a1e40-abb2-7ec4-9f30-5b6c7d8e9fa0",
"status": "processing",
"stage": "contacts",
"progress": {
"processed": 1200,
"total": 3100
},
"sources": [
{
"id": "019a1d90-3b4c-7d5e-9f6a-7b8c9d0e1f2a",
"name": "Clientes ativos",
"contacts_count": 1284
},
{
"id": "019a1d91-4c5d-7e6f-8a7b-8c9d0e1f2a3b",
"name": "Compradores",
"contacts_count": 1900
}
],
"target": {
"id": "019a1d92-5d6e-7f70-9b8c-9d0e1f2a3b4c",
"name": "Clientes (todos)"
},
"delete_sources": false,
"merge_duplicate_contacts": false,
"counts": {
"memberships": 3184,
"unique_contacts": 3100,
"duplicate_memberships": 84,
"contacts_added": 1200,
"contacts_merged": 0,
"items_updated": 0,
"sources_deleted": 0
},
"summary": {
"merged_contacts": [],
"merged_contacts_total": 0,
"items": [],
"kept_sources": []
},
"error": null,
"user_id": null,
"created_at": "2026-10-24T16:00:00.000000Z",
"started_at": "2026-10-24T16:00:01.000000Z",
"completed_at": null
}
#Repetir uma unificação que falhou
POST/segments/merges/{id}/retry
Roda de novo uma unificação com status: "failed"; cada passo continua de onde parou.
Parâmetros de caminho
idstringobrigatórioID da unificação.
Erros
- 409
invalid_merge_statusSó uma unificação que falhou pode ser repetida. - 404
not_foundO recurso não existe ou é de outro time.
cURL
curl -X POST https://api.postal.so/segments/merges/019a1e40-abb2-7ec4-9f30-5b6c7d8e9fa0/retry \
-H "Authorization: Bearer $POSTAL_API_KEY"
Node.js
const response = await fetch('https://api.postal.so/segments/merges/019a1e40-abb2-7ec4-9f30-5b6c7d8e9fa0/retry', {
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/segments/merges/019a1e40-abb2-7ec4-9f30-5b6c7d8e9fa0/retry",
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('segments/merges/019a1e40-abb2-7ec4-9f30-5b6c7d8e9fa0/retry', [
'headers' => ['Authorization' => 'Bearer '.getenv('POSTAL_API_KEY')],
]);
$data = json_decode($response->getBody(), true);
{
"object": "segment_merge",
"id": "019a1e40-abb2-7ec4-9f30-5b6c7d8e9fa0",
"status": "pending",
"stage": "contacts",
"progress": {
"processed": 1200,
"total": 3100
},
"sources": [
{
"id": "019a1d90-3b4c-7d5e-9f6a-7b8c9d0e1f2a",
"name": "Clientes ativos",
"contacts_count": 1284
},
{
"id": "019a1d91-4c5d-7e6f-8a7b-8c9d0e1f2a3b",
"name": "Compradores",
"contacts_count": 1900
}
],
"target": {
"id": "019a1d92-5d6e-7f70-9b8c-9d0e1f2a3b4c",
"name": "Clientes (todos)"
},
"delete_sources": false,
"merge_duplicate_contacts": false,
"counts": {
"memberships": 3184,
"unique_contacts": 3100,
"duplicate_memberships": 84,
"contacts_added": 1200,
"contacts_merged": 0,
"items_updated": 0,
"sources_deleted": 0
},
"summary": {
"merged_contacts": [],
"merged_contacts_total": 0,
"items": [],
"kept_sources": []
},
"error": null,
"user_id": null,
"created_at": "2026-10-24T16:00:00.000000Z",
"started_at": "2026-10-24T16:00:01.000000Z",
"completed_at": null
}