Entrar Criar conta
Audiência

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.

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 e extras
email, first_name, last_nameis, is_not, contains, not_contains, starts_with, is_empty, is_setvalue: texto de até 1.000 caracteres (não vale para is_empty e is_set).
created_atbefore, after, between, in_last_days, more_than_days_agovalue: data AAAA-MM-DD; between leva ["AAAA-MM-DD", "AAAA-MM-DD"]; os de dias levam um número de 1 a 3.650.
unsubscribedisvalue: true ou false.
email_statusis, is_notvalue: lista com valid, invalid, risky e unverified (resultado da validação de e-mails).
consentis, is_notvalue: confirmed ou unconfirmed (se o contato pode receber marketing; veja contatos guardados dos e-mails transacionais).
originis, is_notvalue: transactional (os contatos guardados dos e-mails transacionais).
propertyDepende 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.
taghas, has_nottag_id: ID de uma etiqueta.
topicsubscribed, unsubscribedtopic_id: ID de um tópico.
segmentin, not_insegment_id: ID de um segmento estático.
email_activityperformed, not_performedactivity: 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).
eventperformed, not_performedevent: 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

Chave de acesso total

Cada segmento vem com contacts_count. Para ver os contatos, use GET /contacts?segment_id=….

Parâmetros de consulta

  • limitinteger
    De 1 a 100. Padrão: 20.
  • afterstring
    Cursor da próxima página.
  • beforestring
    Cursor da página anterior.
  • searchstring
    Busca no nome.
  • typestring
    static ou 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);
200Resposta
{
  "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

Chave de acesso total

Corpo da requisição

  • namestringobrigatório
    Nome, até 255 caracteres.
  • descriptionstring | null
    Descrição, até 500 caracteres.
  • typestring
    static (padrão) ou dynamic.
  • filtersobject
    Os filtros. Obrigatórios num segmento dinâmico; um estático não aceita filtros.

Erros

  • 422validation_error
    Algum campo é inválido. A mensagem diz qual.
  • 403plan_limit_reached
    O plano não permite mais segmentos, ou não inclui segmentos dinâmicos.
  • 404not_found
    Uma etiqueta, tópico, segmento, campanha, template ou automação citados nos filtros não 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);
201Resposta
{
  "object": "segment",
  "id": "019a1d90-3b4c-7d5e-9f6a-7b8c9d0e1f2a",
  "name": "Clientes ativos",
  "type": "static"
}

#Criar um segmento dinâmico

POST/segments

Chave de acesso total

O mesmo endpoint, com type: "dynamic" e filters. Use POST /segments/preview antes para ver quantos contatos combinam.

Erros

  • 422validation_error
    Algum campo é inválido. A mensagem diz qual.
  • 403plan_limit_reached
    O plano não permite mais segmentos, ou não inclui 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);
201Resposta
{
  "object": "segment",
  "id": "019a1d90-3b4c-7d5e-9f6a-7b8c9d0e1f2a",
  "name": "Clientes VIP recentes",
  "type": "dynamic"
}

#Visualizar os filtros de um segmento

POST/segments/preview

Chave de acesso total

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ório

Erros

  • 422validation_error
    Algum campo é inválido. A mensagem diz qual.
  • 404not_found
    Algum item citado nos filtros não 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);
200Resposta
{
  "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}

Chave de acesso total

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ório
    ID do segmento.

Erros

  • 404not_found
    O recurso não existe ou é de outro time.
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);
200Resposta
{
  "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}

Chave de acesso total

Mudar os filters de um segmento dinâmico recalcula os membros em segundo plano. O type não muda.

Parâmetros de caminho

  • idstringobrigatório
    ID do segmento.

Corpo da requisição

  • namestring
    Novo nome.
  • descriptionstring | null
    Nova descrição.
  • filtersobject
    Novos filtros (só em segmentos dinâmicos).

Erros

  • 422validation_error
    Algum campo é inválido. A mensagem diz qual.
  • 403paused_by_plan
    O segmento está pausado porque passa do limite do plano.
  • 404not_found
    O recurso não existe ou é de outro time.
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);
200Resposta
{
  "object": "segment",
  "id": "019a1d90-3b4c-7d5e-9f6a-7b8c9d0e1f2a"
}

#Converter em estático

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

Chave de acesso total

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ório
    ID do segmento.

Erros

  • 409invalid_segment_type
    O segmento já é estático.
  • 403paused_by_plan
    O segmento está pausado pelo plano.
  • 404not_found
    O recurso não existe ou é de outro time.
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);
200Resposta
{
  "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}

Chave de acesso total

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ório
    ID do segmento.

Erros

  • 409segment_in_use
    Algum formulário envia contatos para este segmento, ou algum segmento dinâmico confere quem está nele. A mensagem diz quais.
  • 404not_found
    O recurso não existe ou é de outro time.
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);
200Resposta
{
  "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.
automationretarget, keep e, se estiver ativa, pause
form, segment (dinâmico)retarget, keep
contact_importNenhuma: 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

Chave de acesso total

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ório
    De 2 a 20 IDs de segmentos estáticos, sem repetir. O destino pode ser um deles.
  • targetobjectobrigatório
    Quem recebe os contatos: {"segment_id": "…"} de um segmento existente, ou {"name": "…"} para criar um novo (até 255 caracteres).
  • delete_sourcesboolean
    Exclui os segmentos de origem no fim. Padrão: false.

Erros

  • 422validation_error
    Algum campo é inválido. A mensagem diz qual.
  • 404not_found
    Algum segmento não 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);
200Resposta
{
  "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

Chave de acesso total

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ório
    De 2 a 20 IDs de segmentos estáticos, sem repetir. O destino pode ser um deles.
  • targetobjectobrigatório
    Quem recebe os contatos: {"segment_id": "…"} de um segmento existente, ou {"name": "…"} para criar um novo (até 255 caracteres).
  • delete_sourcesboolean
    Exclui os segmentos de origem no fim. Padrão: false.
  • merge_duplicate_contactsboolean
    Junta também os contatos que parecem repetidos (mesma caixa de e-mail). Padrão: false.
  • resolutionsobject
    A escolha de cada item ligado: {"broadcast:019a…": "retarget"}, com retarget, keep, cancel ou pause.

Erros

  • 422validation_error
    Faltou 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.
  • 409merge_in_progress
    Outra unificação do time ainda está rodando.
  • 409segment_in_use
    Há itens que impedem excluir as origens (errors.blocking): espere terminarem ou mantenha os segmentos.
  • 403plan_limit_reached
    Com destino novo e sem excluir as origens, o time passaria do limite de segmentos.
  • 404not_found
    Algum segmento não 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);
202Resposta
{
  "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}

Chave de acesso total

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ório
    ID da unificação.

Erros

  • 404not_found
    O recurso não existe ou é de outro time.
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);
200Resposta
{
  "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

Chave de acesso total

Roda de novo uma unificação com status: "failed"; cada passo continua de onde parou.

Parâmetros de caminho

  • idstringobrigatório
    ID da unificação.

Erros

  • 409invalid_merge_status
    Só uma unificação que falhou pode ser repetida.
  • 404not_found
    O recurso não existe ou é de outro time.
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);
202Resposta
{
  "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
}