Iniciar sesión Crear cuenta
Audiencia

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.

Filtros
{
  "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)OperadoresValor y extras
email, first_name, last_nameis, is_not, contains, not_contains, starts_with, is_empty, is_setvalue: texto de hasta 1.000 caracteres (no vale para is_empty ni is_set).
created_atbefore, after, between, in_last_days, more_than_days_agovalue: 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.
unsubscribedisvalue: true o false.
email_statusis, is_notvalue: lista con valid, invalid, risky y unverified (resultado de la verificación de correos).
consentis, is_notvalue: confirmed o unconfirmed (si el contacto puede recibir marketing; mira contactos guardados de los correos transaccionales).
originis, is_notvalue: transactional (los contactos guardados de los correos transaccionales).
propertyDepende 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.
taghas, has_nottag_id: ID de una etiqueta.
topicsubscribed, unsubscribedtopic_id: ID de un tema.
segmentin, not_insegment_id: ID de un segmento estático.
email_activityperformed, not_performedactivity: 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).
eventperformed, not_performedevent: 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

Clave de acceso total

Cada segmento incluye contacts_count. Para ver los contactos, usa GET /contacts?segment_id=….

Parámetros de consulta

  • limitinteger
    De 1 a 100. Predeterminado: 20.
  • afterstring
    Cursor de la página siguiente.
  • beforestring
    Cursor de la página anterior.
  • searchstring
    Búsqueda por nombre.
  • typestring
    static o dynamic.
GET /segments

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);
200Respuesta
{
  "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

Clave de acceso total

Cuerpo de la solicitud

  • namestringobligatorio
    Nombre, hasta 255 caracteres.
  • descriptionstring | null
    Descripción, hasta 500 caracteres.
  • typestring
    static (predeterminado) o dynamic.
  • filtersobject
    Los filtros. Obligatorios en un segmento dinámico; uno estático no acepta filtros.

Errores

  • 422validation_error
    Algún campo no es válido. El mensaje indica cuál.
  • 403plan_limit_reached
    El plan no permite más segmentos, o no incluye segmentos dinámicos.
  • 404not_found
    Una etiqueta, tema, segmento, campaña, plantilla o automatización citados en los filtros no existe.
POST /segments

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);
201Respuesta
{
  "object": "segment",
  "id": "019a1d90-3b4c-7d5e-9f6a-7b8c9d0e1f2a",
  "name": "Clientes ativos",
  "type": "static"
}

#Crear un segmento dinámico

POST/segments

Clave de acceso total

El mismo endpoint, con type: "dynamic" y filters. Usa antes POST /segments/preview para ver cuántos contactos cumplen los filtros.

Errores

  • 422validation_error
    Algún campo no es válido. El mensaje indica cuál.
  • 403plan_limit_reached
    El plan no permite más segmentos, o no incluye segmentos dinámicos.
POST /segments

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);
201Respuesta
{
  "object": "segment",
  "id": "019a1d90-3b4c-7d5e-9f6a-7b8c9d0e1f2a",
  "name": "Clientes VIP recentes",
  "type": "dynamic"
}

#Previsualizar los filtros de un segmento

POST/segments/preview

Clave de acceso total

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

  • filtersobjectobligatorio
    Los filtros.

Errores

  • 422validation_error
    Algún campo no es válido. El mensaje indica cuál.
  • 404not_found
    Algún elemento citado en los filtros no existe.
POST /segments/preview

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);
200Respuesta
{
  "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}

Clave de acceso total

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

  • idstringobligatorio
    ID del segmento.

Errores

  • 404not_found
    El recurso no existe o es de otro equipo.
GET /segments/{id}

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);
200Respuesta
{
  "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}

Clave de acceso total

Cambiar los filters de un segmento dinámico recalcula los miembros en segundo plano. El type no cambia.

Parámetros de ruta

  • idstringobligatorio
    ID del segmento.

Cuerpo de la solicitud

  • namestring
    Nombre nuevo.
  • descriptionstring | null
    Nueva descripción.
  • filtersobject
    Nuevos filtros (solo en segmentos dinámicos).

Errores

  • 422validation_error
    Algún campo no es válido. El mensaje indica cuál.
  • 403paused_by_plan
    El segmento está pausado porque supera el límite del plan.
  • 404not_found
    El recurso no existe o es de otro equipo.
PATCH /segments/{id}

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);
200Respuesta
{
  "object": "segment",
  "id": "019a1d90-3b4c-7d5e-9f6a-7b8c9d0e1f2a"
}

#Convertir en estático

POST/segments/{id}/convert-to-static

Clave de acceso total

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

  • idstringobligatorio
    ID del segmento.

Errores

  • 409invalid_segment_type
    El segmento ya es estático.
  • 403paused_by_plan
    El segmento está pausado por el plan.
  • 404not_found
    El recurso no existe o es de otro equipo.
POST /segments/{id}/convert-to-static

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);
200Respuesta
{
  "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}

Clave de acceso total

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

  • idstringobligatorio
    ID del segmento.

Errores

  • 409segment_in_use
    Algú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.
  • 404not_found
    El recurso no existe o es de otro equipo.
DELETE /segments/{id}

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);
200Respuesta
{
  "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.
automationretarget, keep y, si está activa, pause
form, segment (dinámico)retarget, keep
contact_importNinguna: 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

Clave de acceso total

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[]obligatorio
    De 2 a 20 IDs de segmentos estáticos, sin repetir. El destino puede ser uno de ellos.
  • targetobjectobligatorio
    Quién recibe los contactos: {"segment_id": "…"} de un segmento existente, o {"name": "…"} para crear uno nuevo (hasta 255 caracteres).
  • delete_sourcesboolean
    Elimina los segmentos de origen al final. Predeterminado: false.

Errores

  • 422validation_error
    Algún campo no es válido. El mensaje indica cuál.
  • 404not_found
    Algún segmento no existe.
POST /segments/merge/preview

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);
200Respuesta
{
  "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

Clave de acceso total

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[]obligatorio
    De 2 a 20 IDs de segmentos estáticos, sin repetir. El destino puede ser uno de ellos.
  • targetobjectobligatorio
    Quién recibe los contactos: {"segment_id": "…"} de un segmento existente, o {"name": "…"} para crear uno nuevo (hasta 255 caracteres).
  • delete_sourcesboolean
    Elimina los segmentos de origen al final. Predeterminado: false.
  • merge_duplicate_contactsboolean
    Une también los contactos que parecen repetidos (mismo buzón de correo). Predeterminado: false.
  • resolutionsobject
    La decisión para cada elemento vinculado: {"broadcast:019a…": "retarget"}, con retarget, keep, cancel o pause.

Errores

  • 422validation_error
    Faltó 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.
  • 409merge_in_progress
    Otra unificación del equipo sigue en ejecución.
  • 409segment_in_use
    Hay elementos que impiden eliminar los orígenes (errors.blocking): espera a que terminen o conserva los segmentos.
  • 403plan_limit_reached
    Con un destino nuevo y sin eliminar los orígenes, el equipo superaría el límite de segmentos.
  • 404not_found
    Algún segmento no existe.
POST /segments/merge

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);
202Respuesta
{
  "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}

Clave de acceso total

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

  • idstringobligatorio
    ID de la unificación.

Errores

  • 404not_found
    El recurso no existe o es de otro equipo.
GET /segments/merges/{id}

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);
200Respuesta
{
  "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

Clave de acceso total

Ejecuta de nuevo una unificación con status: "failed"; cada paso continúa desde donde se detuvo.

Parámetros de ruta

  • idstringobligatorio
    ID de la unificación.

Errores

  • 409invalid_merge_status
    Solo se puede reintentar una unificación que falló.
  • 404not_found
    El recurso no existe o es de otro equipo.
POST /segments/merges/{id}/retry

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);
202Respuesta
{
  "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
}