Segmentos
Listas de contactos para dirigir campañas.
Los segmentos son listas de contactos para dirigir campañas. Un contacto puede estar en varios segmentos. Añade y quita contactos mediante los endpoints de contactos, en la importación o con formularios. El plan Gratis permite hasta 3 segmentos; los planes de pago, los que quieras.
Hay dos tipos, en el campo type: static (predeterminado), una lista que montas contacto a contacto, y dynamic, definida por filtros y actualizada automáticamente a medida que cambian los contactos. Solo los segmentos estáticos aceptan contactos a mano (mediante los endpoints de contactos, la importación y los formularios); intentarlo en uno dinámico da 422. El tipo no cambia una vez creado, pero un segmento dinámico puede convertirse en estático.
#Segmentos dinámicos
Un segmento dinámico guarda a quienes cumplen los filtros ahora. Al crear o cambiar los filtros, los miembros se calculan en segundo plano (sin iniciar automatizaciones); después, la lista se actualiza cuando un contacto cambia y, para los filtros que dependen del tiempo ("en los últimos 30 días"), cada hora. Cuando un contacto entra o sale, Postal dispara los webhooks contact.segment_entered y contact.segment_exited y las automatizaciones con el disparador de segmento añadido o quitado. El contacts_count de un segmento dinámico es el número de contactos que cumplen los filtros, guardado hasta por un minuto. Crear un segmento dinámico exige que el plan lo incluya (403 plan_limit_reached si no lo incluye); los ya creados siguen funcionando.
Los filtros son grupos de condiciones. Cada grupo combina sus condiciones con match (all: todas, any: cualquiera) y el segmento combina los grupos del mismo modo. Puede haber hasta 10 grupos y 20 condiciones en total. Las comparaciones de texto no distinguen mayúsculas de minúsculas, y los operadores negativos (is_not, not_contains, has_not…) también incluyen a quienes no tienen valor. Postal lo comprueba todo (propiedades, etiquetas, temas, campañas…) y devuelve los filtros en su 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 y extras |
|---|---|---|
email, first_name, last_name | is, is_not, contains, not_contains, starts_with, is_empty, is_set | value: texto de hasta 1.000 caracteres (no vale para is_empty ni is_set). |
created_at | before, after, between, in_last_days, more_than_days_ago | value: fecha AAAA-MM-DD; between lleva ["AAAA-MM-DD", "AAAA-MM-DD"]; los de días llevan un número de 1 a 3.650. |
unsubscribed | is | value: true o false. |
email_status | is, is_not | value: lista con valid, invalid, risky y unverified (resultado de la verificación de correos). |
consent | is, is_not | value: confirmed o unconfirmed (si el contacto puede recibir marketing; mira contactos guardados de los correos transaccionales). |
origin | is, is_not | value: transactional (los contactos guardados de los correos transaccionales). |
property | Depende del tipo de la propiedad: texto como arriba; número: is, is_not, greater_than, less_than, between, is_empty, is_set; booleano: is, is_empty, is_set; fecha: como created_at, más is_empty e is_set. | key: la clave de la propiedad; between de números lleva [mínimo, máximo]; is: false también incluye a quienes no tienen valor. |
tag | has, has_not | tag_id: ID de una etiqueta. |
topic | subscribed, unsubscribed | topic_id: ID de un tema. |
segment | in, not_in | segment_id: ID de un segmento estático. |
email_activity | performed, not_performed | activity: received, opened, clicked, replied, bounced o complained. Opcionales: uno entre broadcast_id, template_id y automation_id; within_days (1 a 3.650; vacío: desde siempre); min_count (1 a 1.000; predeterminado 1). |
event | performed, not_performed | event: el nombre del evento. Opcionales: within_days, min_count y property ({"key": "plan", "value": "pro"}: solo cuentan los eventos con ese valor). |
#Listar segmentos
GET/segments
Cada segmento incluye contacts_count. Para ver los contactos, usa GET /contacts?segment_id=….
Parámetros de consulta
limitintegerDe 1 a 100. Predeterminado: 20.afterstringCursor de la página siguiente.beforestringCursor de la página anterior.searchstringBúsqueda por nombre.typestringstaticodynamic.
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
}
]
}
]
}
}
]
}
#Crear un segmento
POST/segments
Cuerpo de la solicitud
namestringobligatorioNombre, hasta 255 caracteres.descriptionstring | nullDescripción, hasta 500 caracteres.typestringstatic(predeterminado) odynamic.filtersobjectLos filtros. Obligatorios en un segmento dinámico; uno estático no acepta filtros.
Errores
- 422
validation_errorAlgún campo no es válido. El mensaje indica cuál. - 403
plan_limit_reachedEl plan no permite más segmentos, o no incluye segmentos dinámicos. - 404
not_foundUna etiqueta, tema, segmento, campaña, plantilla o automatización citados en los filtros no 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"
}
#Crear un segmento dinámico
POST/segments
El mismo endpoint, con type: "dynamic" y filters. Usa antes POST /segments/preview para ver cuántos contactos cumplen los filtros.
Errores
- 422
validation_errorAlgún campo no es válido. El mensaje indica cuál. - 403
plan_limit_reachedEl plan no permite más segmentos, o no incluye 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"
}
#Previsualizar los filtros de un segmento
POST/segments/preview
Comprueba los filtros y dice cuántos contactos los cumplen ahora, sin crear nada. Devuelve los filtros en su forma canónica y hasta 10 contactos de ejemplo (los más recientes).
Cuerpo de la solicitud
filtersobjectobligatorioLos filtros.
Errores
- 422
validation_errorAlgún campo no es válido. El mensaje indica cuál. - 404
not_foundAlgún elemento citado en los filtros no 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"
}
]
}
]
}
#Obtener un segmento
GET/segments/{id}
Un segmento dinámico incluye además los filters. paused_by_plan es true cuando el plan dejó de admitir el segmento: sigue en la lista y se puede leer o eliminar, pero no se puede modificar (403 paused_by_plan).
Parámetros de ruta
idstringobligatorioID del segmento.
Errores
- 404
not_foundEl recurso no existe o es de otro equipo.
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"
}
#Actualizar un segmento
PATCH/segments/{id}
Cambiar los filters de un segmento dinámico recalcula los miembros en segundo plano. El type no cambia.
Parámetros de ruta
idstringobligatorioID del segmento.
Cuerpo de la solicitud
namestringNombre nuevo.descriptionstring | nullNueva descripción.filtersobjectNuevos filtros (solo en segmentos dinámicos).
Errores
- 422
validation_errorAlgún campo no es válido. El mensaje indica cuál. - 403
paused_by_planEl segmento está pausado porque supera el límite del plan. - 404
not_foundEl recurso no existe o es de otro equipo.
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"
}
#Convertir en estático
POST/segments/{id}/convert-to-static
Los contactos que cumplen los filtros ahora pasan a ser la lista de un segmento estático, que los conserva pase lo que pase. Las campañas, automatizaciones y formularios que usan el segmento siguen apuntando a él.
Parámetros de ruta
idstringobligatorioID del segmento.
Errores
- 409
invalid_segment_typeEl segmento ya es estático. - 403
paused_by_planEl segmento está pausado por el plan. - 404
not_foundEl recurso no existe o es de otro equipo.
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"
}
#Eliminar un segmento
DELETE/segments/{id}
Elimina solo la lista: los contactos permanecen en la audiencia. Un segmento que recibe los contactos de un formulario no se puede eliminar; cambia antes el segmento del formulario.
Un segmento estático que algún segmento dinámico usa en sus filtros tampoco se puede eliminar: cambia antes los filtros.
Parámetros de ruta
idstringobligatorioID del segmento.
Errores
- 409
segment_in_useAlgún formulario envía contactos a este segmento, o algún segmento dinámico comprueba quién está en él. El mensaje indica cuáles. - 404
not_foundEl recurso no existe o es de otro equipo.
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
Une varios segmentos estáticos en uno solo. El flujo tiene dos etapas: la vista previa responde al instante, con los recuentos, los contactos que parecen repetidos y todo lo que apunta a los segmentos de origen; la unificación exige una decisión para cada elemento vinculado (resolutions) y se ejecuta en segundo plano, con el progreso en GET /segments/merges/{id}. Solo se ejecuta una unificación a la vez por equipo. Los segmentos dinámicos no entran: conviértelos antes en estáticos.
Elemento vinculado (kind) | Opciones (resolutions) |
|---|---|
broadcast (borrador) | retarget, keep |
broadcast (programada) | retarget, keep, cancel |
broadcast (en cola, enviándose o pausada) | Ninguna: mantiene el segmento e impide eliminar el origen hasta que termine. |
automation | retarget, keep y, si está activa, pause |
form, segment (dinámico) | retarget, keep |
contact_import | Ninguna: una importación en curso impide eliminar el origen. |
retarget apunta el elemento al segmento de destino, keep lo deja como está (solo si los orígenes no se eliminan), cancel cancela la programación y pause pausa la automatización para revisarla. La clave de cada elemento es kind:id, como broadcast:019a…; la vista previa lista los elementos en impact, con las choices y el default de cada uno.
#Previsualizar una unificación
POST/segments/merge/preview
No modifica nada. Devuelve, en counts, las suscripciones (memberships), los contactos únicos, las suscripciones repetidas y cuántos contactos son nuevos en el destino; en possible_duplicates, grupos de contactos que parecen el mismo buzón de correo con direcciones distintas; en impact, los elementos vinculados (consulta la tabla de arriba); en blocking, las claves de los elementos que impiden eliminar los orígenes; y en warnings, avisos sobre la eliminación.
Cuerpo de la solicitud
source_idsstring[]obligatorioDe 2 a 20 IDs de segmentos estáticos, sin repetir. El destino puede ser uno de ellos.targetobjectobligatorioQuién recibe los contactos:{"segment_id": "…"}de un segmento existente, o{"name": "…"}para crear uno nuevo (hasta 255 caracteres).delete_sourcesbooleanElimina los segmentos de origen al final. Predeterminado:false.
Errores
- 422
validation_errorAlgún campo no es válido. El mensaje indica cuál. - 404
not_foundAlgún segmento no 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
Inicia la unificación en segundo plano y devuelve la unificación (202). Acepta el encabezado Idempotency-Key. Los elementos vinculados que la vista previa listó deben tener una decisión en resolutions; los que no tienen opciones (como una campaña en envío) impiden eliminar los orígenes.
Cuerpo de la solicitud
source_idsstring[]obligatorioDe 2 a 20 IDs de segmentos estáticos, sin repetir. El destino puede ser uno de ellos.targetobjectobligatorioQuién recibe los contactos:{"segment_id": "…"}de un segmento existente, o{"name": "…"}para crear uno nuevo (hasta 255 caracteres).delete_sourcesbooleanElimina los segmentos de origen al final. Predeterminado:false.merge_duplicate_contactsbooleanUne también los contactos que parecen repetidos (mismo buzón de correo). Predeterminado:false.resolutionsobjectLa decisión para cada elemento vinculado:{"broadcast:019a…": "retarget"}, conretarget,keep,cancelopause.
Errores
- 422
validation_errorFaltó decidir qué hacer con algún elemento vinculado (errors.missing), la opción no existe para el elemento (errors.invalid), algún segmento es dinámico o los datos no son válidos. - 409
merge_in_progressOtra unificación del equipo sigue en ejecución. - 409
segment_in_useHay elementos que impiden eliminar los orígenes (errors.blocking): espera a que terminen o conserva los segmentos. - 403
plan_limit_reachedCon un destino nuevo y sin eliminar los orígenes, el equipo superaría el límite de segmentos. - 404
not_foundAlgún segmento no 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
}
#Obtener una unificación
GET/segments/merges/{id}
status: pending, processing, completed o failed (con error). progress muestra los elementos procesados en el paso actual (stage), y counts y summary resumen lo que se hizo.
Parámetros de ruta
idstringobligatorioID de la unificación.
Errores
- 404
not_foundEl recurso no existe o es de otro equipo.
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
}
#Reintentar una unificación que falló
POST/segments/merges/{id}/retry
Ejecuta de nuevo una unificación con status: "failed"; cada paso continúa desde donde se detuvo.
Parámetros de ruta
idstringobligatorioID de la unificación.
Errores
- 409
invalid_merge_statusSolo se puede reintentar una unificación que falló. - 404
not_foundEl recurso no existe o es de otro equipo.
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
}