Entrar Criar conta
Referência da API

Planos

A lista pública de planos, preços e limites e, para o dono e os admins, o Adicional de envios do plano de envio e o Adicional de contatos do plano de marketing.

A lista pública de planos, a mesma usada na página de preços. Não precisa de chave de API. Mais abaixo, o Adicional de envios do plano de envio e o Adicional de contatos do plano de marketing, que o dono e os admins gerenciam com a sessão do painel.

#Listar planos

GET/plans

Público, sem chave

Planos ativos e públicos, na ordem de exibição, dos dois produtos: kind é transactional (envio) ou marketing (contatos e campanhas). Preços em prices estão em centavos, por moeda (brl, usd e eur) e intervalo (month, year); overage_prices é o preço por mil além do limite, quando o plano permite (allows_overage): e-mails além da cota mensal nos planos de envio e contatos além do limite nos planos de marketing (overage_unit diz qual). Nos limits, null é ilimitado, 0 é não incluído e rate_limit é o número de requisições por segundo. A resposta também traz o feature_catalog, e o idioma dos textos segue o cabeçalho Accept-Language (pt-BR ou en); sem ele, os textos vêm em português.

Limites e recursos. limits traz todas as chaves do produto do plano, já resolvidas: números (null ilimitado, 0 não incluído), recursos liga/desliga (1 incluído, 0 não; postal_branding é invertido: 1 é ter o selo) e recursos informativos. O feature_catalog descreve cada chave: sections, cada uma com key, label e features, e cada recurso com key, label, description, type (number ou boolean), unit e unit_one (plural e singular, só nos números), inverted (só quando verdadeiro), kind (transactional, marketing ou both) e enforced (a API já recusa o que o plano não inclui). Veja o que cada chave bloqueia em Limites e recursos do plano.

Faixas definidas pela Postal. A escala de volume do painel é feita de faixas que a Postal cadastra por produto (volume_bands: {"transactional": [{"id", "volume"}], "marketing": [...]}, na ordem cadastrada; vazio enquanto o produto não tem faixas). Cada plano diz quais atende: bands lista os volumes das faixas que ele atende, em ordem crescente, e cada preço em prices traz o volume_band_id da faixa a que serve (ou null). Um plano que não atende uma faixa simplesmente não vende aquele volume; acima da última faixa só o plano sob medida atende. O volume de um preço ligado a uma faixa é o da faixa. Sem faixas cadastradas, vale o que está descrito a seguir.

Faixas de volume. Um plano pode ter vários preços por moeda e intervalo, um para cada faixa: volume é o volume mensal da faixa (e-mails no plano de envio, contatos no de marketing; volume_unit diz qual) e vale no lugar de emails_per_month ou contacts de limits para quem assina aquela faixa. emails_per_month, dentro do preço, repete volume nos planos de envio. overage_tiers lista o preço do excedente de cada faixa, e overage_prices resume o preço por moeda. O time liga o excedente de cada produto no painel; cada pacote de 1.000 começado entra na próxima fatura. purchasable é false em um plano ou preço que não pode ser comprado (não está ligado ao Stripe), e is_default marca o plano em que todo time começa. highlights é o que o cartão de preços do site destaca: null (padrão compacto) ou {"inherit", "items"}, com inherit abrindo o cartão com “Tudo do plano anterior, mais:” e items em ordem, cada um {"type": "feature", "key"} ou {"type": "text", "text"}.

Adicional de contatos. contact_packs lista os pacotes do Adicional de contatos que estão à venda para os planos de marketing pagos: [{"object": "contact_pack", "id", "name", "contacts", "max_quantity", "interval", "prices": [{"currency", "amount"}]}], com os preços em centavos. A lista vem vazia enquanto não há nenhum à venda. Veja Adicional de contatos.

GET /plans

cURL

curl https://api.postal.so/plans

Node.js

const response = await fetch('https://api.postal.so/plans');

const data = await response.json();

Python

import requests

response = requests.get(
    "https://api.postal.so/plans",
    timeout=10,
)

data = response.json()

PHP

$client = new \GuzzleHttp\Client(['base_uri' => 'https://api.postal.so']);

$response = $client->get('plans');

$data = json_decode($response->getBody(), true);
200Resposta (resumida, com valores ilustrativos)
{
  "object": "list",
  "data": [
    {
      "object": "plan",
      "id": "0199e000-1a2b-7c3d-8e4f-5a6b7c8d9e0f",
      "kind": "transactional",
      "slug": "pro",
      "name": "Pro",
      "description": "Para produtos em produção com volume constante.",
      "features": [
        "50.000 e-mails por mês",
        "Sem limite diário",
        "10 domínios"
      ],
      "highlights": {
        "inherit": true,
        "items": [
          {
            "type": "feature",
            "key": "emails_per_month"
          },
          {
            "type": "feature",
            "key": "domains"
          },
          {
            "type": "text",
            "text": "Suporte por e-mail"
          }
        ]
      },
      "limits": {
        "emails_per_month": 50000,
        "emails_per_day": null,
        "domains": 10,
        "inboxes": 10,
        "custom_inbound_domains": 1,
        "postal_branding": 0,
        "team_members": 10,
        "automation_runs": 10000,
        "ai_credits": 100,
        "layouts": null,
        "rate_limit": 10,
        "automation_experiments": 1,
        "batch_sending": 1,
        "scheduled_sending": 1,
        "attachments_mb": null,
        "engagement_tracking": 1,
        "custom_domain_records": 1,
        "test_addresses": 1,
        "smtp_access": 1,
        "shared_email_links": 1,
        "inbox_agent": 1,
        "templates": null,
        "api_keys": null,
        "webhooks": null,
        "mcp_access": 1,
        "exports": 1,
        "retention_days": 30,
        "automations": null,
        "restricted_api_keys": 1,
        "priority_support": 0,
        "uptime_sla": 0,
        "dedicated_manager": 0
      },
      "allows_overage": true,
      "trial_days": 0,
      "is_default": false,
      "purchasable": true,
      "volume_unit": "emails",
      "overage_unit": "emails",
      "bands": [50000, 100000],
      "prices": [
        {
          "id": "0199e000-2b3c-7d4e-8f5a-6b7c8d9e0f1a",
          "currency": "brl",
          "interval": "month",
          "amount": 9900,
          "volume": 50000,
          "volume_band_id": "0199e000-5e70-7b82-9d9e-0f1a2b3c4d5e",
          "emails_per_month": 50000,
          "purchasable": true
        },
        {
          "id": "0199e000-2b3d-7e5f-9a6b-7c8d9e0f1a2b",
          "currency": "usd",
          "interval": "month",
          "amount": 2000,
          "volume": 50000,
          "volume_band_id": "0199e000-5e70-7b82-9d9e-0f1a2b3c4d5e",
          "emails_per_month": 50000,
          "purchasable": true
        },
        {
          "id": "0199e000-2b3e-7f60-8a7b-8c9d0e1f2a3b",
          "currency": "brl",
          "interval": "month",
          "amount": 17900,
          "volume": 100000,
          "volume_band_id": "0199e000-5e71-7c93-8eaf-1a2b3c4d5e6f",
          "emails_per_month": 100000,
          "purchasable": true
        },
        {
          "id": "0199e000-2b3f-7071-9b8c-9d0e1f2a3b4c",
          "currency": "usd",
          "interval": "month",
          "amount": 3600,
          "volume": 100000,
          "volume_band_id": "0199e000-5e71-7c93-8eaf-1a2b3c4d5e6f",
          "emails_per_month": 100000,
          "purchasable": true
        },
        {
          "id": "0199e000-2b40-7182-8c9d-0e1f2a3b4c5d",
          "currency": "eur",
          "interval": "month",
          "amount": 1900,
          "volume": 50000,
          "volume_band_id": "0199e000-5e70-7b82-9d9e-0f1a2b3c4d5e",
          "emails_per_month": 50000,
          "purchasable": true
        }
      ],
      "overage_prices": {
        "brl": 450,
        "usd": 90,
        "eur": 80
      },
      "overage_tiers": [
        {
          "volume": 50000,
          "currency": "brl",
          "amount": 450
        },
        {
          "volume": 50000,
          "currency": "usd",
          "amount": 90
        },
        {
          "volume": 50000,
          "currency": "eur",
          "amount": 80
        },
        {
          "volume": 100000,
          "currency": "brl",
          "amount": 400
        },
        {
          "volume": 100000,
          "currency": "usd",
          "amount": 80
        }
      ]
    },
    {
      "object": "plan",
      "id": "0199e000-3c4d-7e5f-8a6b-7c8d9e0f1a2b",
      "kind": "marketing",
      "slug": "pro",
      "name": "Pro",
      "description": "Para quem envia campanhas e automações a uma audiência em crescimento.",
      "features": [],
      "highlights": null,
      "limits": {
        "contacts": 5000,
        "segments": null,
        "broadcasts_per_month": null,
        "email_verifications_per_month": 5000,
        "crm_connections": 1,
        "app_integrations": 3,
        "dynamic_segments": 1,
        "topics": null,
        "forms": null,
        "form_analytics": 1,
        "priority_support": 0,
        "uptime_sla": 0,
        "dedicated_manager": 0
      },
      "allows_overage": true,
      "trial_days": 0,
      "is_default": false,
      "purchasable": true,
      "volume_unit": "contacts",
      "overage_unit": "contacts",
      "bands": [],
      "prices": [
        {
          "id": "0199e000-4d5e-7f60-9b7c-8d9e0f1a2b3c",
          "currency": "brl",
          "interval": "month",
          "amount": 14900,
          "volume": 5000,
          "emails_per_month": null,
          "purchasable": true
        }
      ],
      "overage_prices": {
        "brl": 700
      },
      "overage_tiers": [
        {
          "volume": 5000,
          "currency": "brl",
          "amount": 700
        }
      ]
    }
  ],
  "volume_bands": {
    "transactional": [
      {
        "id": "0199e000-5e6f-7a71-8c8d-9e0f1a2b3c4d",
        "volume": 3000
      },
      {
        "id": "0199e000-5e70-7b82-9d9e-0f1a2b3c4d5e",
        "volume": 50000
      },
      {
        "id": "0199e000-5e71-7c93-8eaf-1a2b3c4d5e6f",
        "volume": 100000
      }
    ],
    "marketing": []
  },
  "feature_catalog": {
    "object": "feature_catalog",
    "sections": [
      {
        "key": "sending",
        "label": "Envio e recebimento",
        "features": [
          {
            "key": "emails_per_month",
            "label": "E-mails por mês",
            "description": "Cota mensal de envio (mês UTC). Ao atingi-la, a API recusa novos envios, a menos que o plano permita excedente.",
            "type": "number",
            "unit": "e-mails",
            "unit_one": "e-mail",
            "kind": "transactional",
            "enforced": true
          },
          {
            "key": "smtp_access",
            "label": "Envio por SMTP",
            "description": "Enviar pelo servidor SMTP (smtp.postal.so) com uma chave de API como senha.",
            "type": "boolean",
            "kind": "transactional",
            "enforced": true
          },
          {
            "key": "postal_branding",
            "label": "Selo “Enviado com Postal”",
            "description": "O selo da Postal nos e-mails enviados e nos formulários públicos. Nos planos pagos costuma ficar desligado.",
            "type": "boolean",
            "inverted": true,
            "kind": "transactional",
            "enforced": true
          }
        ]
      },
      {
        "key": "inboxes",
        "…": "…"
      }
    ]
  },
  "contact_packs": [
    {
      "object": "contact_pack",
      "id": "019a2e10-4b5c-7d6e-8f70-1a2b3c4d5e6f",
      "name": "Pacote A",
      "contacts": 10000,
      "max_quantity": 10,
      "interval": "month",
      "prices": [
        {
          "currency": "brl",
          "amount": 4900
        },
        {
          "currency": "usd",
          "amount": 900
        }
      ]
    }
  ]
}

#Adicional de envios

Quando o plano de envio do time tem um limite mensal e oferece o Adicional de envios, o dono ou um admin compra pacotes de envios extras, num pagamento único (não é uma assinatura). Os créditos formam uma reserva, à parte da cota do plano, e só são usados depois que a cota mensal acaba. O guia do painel mostra o fluxo na tela: Adicional de envios.

Só com a sessão do painel

As duas rotas abaixo usam a sessão do painel de um dono ou admin do time (GET /billing/email-credits também responde a outros membros, mas sem as compras e sem poder comprar). Uma chave de API, de acesso total ou só de envio, recebe 403 na compra. Por isso os exemplos mostram a requisição como o painel a envia, sem cabeçalho de chave. Para ler a reserva com uma chave, use emails.credits de GET /usage.

  • Ordem de uso. A cota mensal do plano, depois a reserva (os créditos que vencem antes primeiro, os sem validade por último), depois o excedente, se o time o ligou (só os e-mails que a reserva não cobriu são cobrados), e então a recusa: 429 monthly_quota_exceeded.
  • Um crédito por e-mail, contado como a cota conta. Os e-mails para os endereços de teste não gastam crédito. Um lote que a reserva não cobre (sem excedente ligado) é recusado inteiro, sem gastar crédito.
  • Campanhas não usam a reserva, mas contam na mesma cota mensal.
  • O limite diário do plano continua valendo: a reserva não o levanta, e o envio recebe 429 daily_quota_exceeded.
  • Validade. Cada pacote tem a sua, em dias (validity_days, contados da confirmação do pagamento), ou nenhuma: com validity_days: 0, expires_at é null e os créditos não vencem.
  • Estorno. Reembolsar o pagamento retira só os créditos que ainda não foram usados; o que já saiu da reserva continua usado.
  • Quem pode comprar. Só o dono e os admins. Um plano sem limite mensal não tem o que estender: 403 plan_limit_reached. Time cobrado por um parceiro: 403 billed_by_partner.
  • Pagamento. Cartão e, quando os preços do pacote estão em reais e o Pix está ligado, Pix (payment_methods). No Pix, os créditos entram quando o pagamento é confirmado.

Onde a reserva aparece nas respostas

RotaCampoO que traz
GET /usageemails.creditsA reserva ao lado da cota: emails.monthly continua com o used e o limit do plano. Chaves só de envio veem o saldo, mas não a lista grants.
GET /billingemails.monthly, email_creditsO resumo do time, só com a sessão do painel (dono ou admin): veja a tabela abaixo.
GET /billing/email-creditstoda a respostaA visão da seção Adicional de envios: a reserva, os pacotes à venda e as compras.

Em GET /billing, a cota do plano e a reserva vêm separadas:

CampoO que diz
emails.monthlyA cota mensal do plano: used, limit (null: sem limite) e resets_at. Os créditos nunca são somados a ela.
email_creditsA reserva, no mesmo formato de balance em GET /billing/email-credits: reserve, balance, used_this_month, in_use, next_expires_at e grants.

#Ver o Adicional de envios do time

GET/billing/email-credits

Sessão do painel (dono ou admin)

O que a seção Adicional de envios mostra: a reserva, os pacotes à venda e, para o dono e os admins, o histórico de compras.

available é false quando a cobrança não está no ar. included diz se o plano tem um limite mensal de envios que a reserva pode estender (false num plano ilimitado; então packs vem vazio). can_purchase é true para o dono e os admins de um time cobrado pela Postal, num plano com limite mensal. payment_methods lista card e, quando o Pix está ligado, pix.

balance é a reserva: balance (os créditos que dá para usar agora), used_this_month, in_use (true quando a cota mensal acabou e há saldo), next_expires_at (o primeiro vencimento, ou null) e grants, os pacotes e bônus com saldo na ordem em que são usados; expires_at: null é um pacote sem validade. packs são os pacotes à venda: emails, amount (centavos, na moeda principal currency), prices (um preço por moeda), validity_days (0: sem validade) e popular. purchases traz as 50 compras e bônus mais recentes (vazio para quem não é dono nem admin): status é pending (Pix ainda não pago), active, depleted, expired, failed, refunded ou revoked; source é purchase ou staff (bônus); emails é o que veio no pacote e remaining o que resta; receipt_url é o recibo, quando há.

Erros

  • 403invalid_access
    Nenhum time está selecionado na sessão.
Requisição
GET /billing/email-credits HTTP/1.1
Host: api.postal.so
200Resposta (valores ilustrativos)
{
  "object": "email_credits",
  "available": true,
  "included": true,
  "can_purchase": true,
  "payment_methods": ["card", "pix"],
  "balance": {
    "reserve": true,
    "balance": 20000,
    "used_this_month": 0,
    "in_use": false,
    "next_expires_at": "2027-01-15T12:00:00Z",
    "grants": [
      {
        "id": "019a3f20-6c7d-7e8f-9a01-2b3c4d5e6f70",
        "source": "purchase",
        "name": "Pacote A",
        "emails": 20000,
        "remaining": 20000,
        "expires_at": "2027-01-15T12:00:00Z"
      }
    ]
  },
  "packs": [
    {
      "object": "email_credit_pack",
      "id": "019a3f20-5b6c-7d7e-8f90-1a2b3c4d5e6f",
      "name": "Pacote A",
      "emails": 20000,
      "amount": 4900,
      "currency": "brl",
      "prices": [
        {
          "currency": "brl",
          "amount": 4900
        },
        {
          "currency": "usd",
          "amount": 900
        }
      ],
      "validity_days": 90,
      "popular": true
    },
    {
      "object": "email_credit_pack",
      "id": "019a3f20-5b6d-7e8f-9a01-2b3c4d5e6f71",
      "name": "Pacote B",
      "emails": 100000,
      "amount": 19900,
      "currency": "brl",
      "prices": [
        {
          "currency": "brl",
          "amount": 19900
        },
        {
          "currency": "usd",
          "amount": 3900
        }
      ],
      "validity_days": 0,
      "popular": false
    }
  ],
  "purchases": [
    {
      "object": "email_credit_grant",
      "id": "019a3f20-6c7d-7e8f-9a01-2b3c4d5e6f70",
      "source": "purchase",
      "status": "active",
      "name": "Pacote A",
      "emails": 20000,
      "remaining": 20000,
      "amount": 4900,
      "currency": "brl",
      "validity_days": 90,
      "expires_at": "2027-01-15T12:00:00Z",
      "paid_at": "2026-10-17T12:00:00Z",
      "refunded_at": null,
      "receipt_url": "https://receipts.example.com/r/abc123",
      "created_at": "2026-10-17T11:58:40Z"
    }
  ]
}

#Comprar um pacote do Adicional de envios

POST/billing/email-credits/checkout

Sessão do painel (dono ou admin)

Cria a compra de um pacote e devolve o endereço da página de pagamento (url): leve a pessoa até ele. A compra ainda não existe como crédito: os envios entram na reserva quando o pagamento é confirmado (na hora no cartão; no Pix, quando ele é pago). O pagamento é único e usa a moeda da cobrança do time quando o pacote tem preço nela; senão, a moeda principal do pacote.

Ao terminar ou desistir, o navegador volta para Configurações › Cobrança do painel com ?extra_sends=success (pagamento recebido) ou ?extra_sends=canceled (compra cancelada, nada cobrado). Para saber se os créditos já entraram, leia GET /billing/email-credits de novo: a compra aparece em purchases como pending e passa a active. A rota tem limite de requisições mais baixo que o resto da API, como as outras que mexem na cobrança.

Corpo da requisição

Erros

  • 403forbidden
    Quem chamou não é dono nem admin do time, ou usou uma chave de API (estas rotas só aceitam a sessão do painel).
  • 403billed_by_partner
    O time é cobrado por um parceiro, que contrata o adicional.
  • 403plan_limit_reached
    O plano não tem limite mensal de envios, então não há o que estender.
  • 422validation_error
    pack_id ausente, que não é um UUID ou inexistente, ou um pacote que não está mais à venda (a mensagem diz qual).
  • 503billing_unavailable
    A cobrança não está disponível agora.
Requisição
POST /billing/email-credits/checkout HTTP/1.1
Host: api.postal.so
Content-Type: application/json

{
  "pack_id": "019a3f20-5b6c-7d7e-8f90-1a2b3c4d5e6f"
}
200Resposta (valor ilustrativo)
{
  "url": "https://checkout.example.com/c/pay/cs_abc123"
}

#Adicional de contatos

Quando o plano de marketing do time oferece o Adicional de contatos, o dono ou um admin soma contatos ao limite sem trocar de plano. Cada pacote tem um preço recorrente, somado ao da assinatura de marketing, e o limite de contatos passa a ser contatos do plano + contatos de cada pacote × quantidade. Os pacotes só existem para um plano de marketing pago e ativo, e só os que a Postal colocou à venda: GET /plans traz os que estão à venda em contact_packs (vazio quando não há nenhum). O guia do painel mostra o fluxo na tela: Adicional de contatos.

Só com a sessão do painel

As três rotas abaixo usam a sessão do painel de um dono ou admin do time. Uma chave de API, de acesso total ou só de envio, recebe 403, e os outros membros do time também. Por isso os exemplos mostram a requisição como o painel a envia, sem cabeçalho de chave. Para ler o limite com os pacotes usando uma chave, use GET /usage.

  • Aumentar. É cobrado na hora, de forma proporcional ao resto do período, e depois a cada renovação, na mesma fatura da assinatura. O limite só cresce depois que o pagamento é confirmado: confirmado na hora, a resposta é 200 com action: "updated"; se o pagamento precisa de confirmação (a autenticação do cartão, por exemplo), é 202 com action: "payment_pending" e o invoice_url da fatura a pagar, e o limite não muda até ela ser paga. Pagamento recusado: 422 payment_failed, e nada muda.
  • Reduzir ou remover. Vale na próxima renovação, sem reembolso, e o limite continua o mesmo até lá (action: "scheduled"). Pedir de novo a quantidade atual desiste da redução agendada (action: "reduction_canceled"). A redução é recusada com 422 downgrade_blocked (e a lista blockers) se o time tem mais contatos do que o limite que sobraria, e também se a assinatura termina no fim do período.
  • Vários do mesmo pacote. Dá para ter mais de um, até o max_quantity do pacote por time.
  • Quem pode. Só com plano de marketing pago e assinatura ativa. Sem assinatura paga (plano Grátis), ou com a assinatura em teste ou com pagamento pendente: 403 contact_packs_unavailable. Time cobrado por um parceiro: 403 billed_by_partner. A moeda e a cobrança (mensal ou anual) do pacote precisam ser as da assinatura.
  • Plano e assinatura. Os pacotes ficam quando o plano muda (upgrade ou mudança agendada) e somam ao limite do plano novo. Terminam junto com a assinatura, e o time volta ao limite do plano em que fica. Se o limite de contatos do time foi ajustado individualmente, esse número vale como o final e os pacotes não somam a ele.

Onde os pacotes aparecem nas respostas

RotaCampoO que traz
GET /planscontact_packsOs pacotes à venda, sem as quantidades do time: object, id, name, contacts (quantos contatos cada um soma), max_quantity, interval e prices ([{"currency", "amount"}], em centavos).
GET /usagecontactslimit é o limite que vale (plano + pacotes); plan_limit, só o do plano; packs traz contacts (o que os pacotes somam), effective, scheduled e, para chaves de acesso total, items. Chaves só de envio veem os totais, mas não quais pacotes o time tem.
GET /billingcontact_packsO resumo do time, só com a sessão do painel (dono ou admin): veja a tabela abaixo.
GET /billing/contact-packstoda a respostaA visão da tela Adicional de contatos: o limite, a assinatura e os pacotes à venda.

O resumo do time (contact_packs de GET /billing) tem estes campos:

CampoO que diz
plan_limitOs contatos do plano (null: sem limite).
pack_contactsOs contatos que os pacotes somam agora.
effective_limitO limite que vale agora: plan_limit + pack_contacts (null: sem limite).
itemsOs pacotes que o time tem: pack_id, name, contacts, quantity, scheduled_quantity (a quantidade depois da renovação, ou null sem redução agendada), contacts_total (contacts × quantity), max_quantity, amount (centavos, por unidade), currency e interval.
schedulednull, ou a redução que espera a renovação: effective_at, pack_contacts e effective_limit (o limite depois dela).

#Ver os pacotes de contatos do time

GET/billing/contact-packs

Sessão do painel (dono ou admin)

O que a tela Adicional de contatos mostra: o limite, a assinatura de marketing e os pacotes à venda na moeda e na cobrança da assinatura, mais os que o time já tem (mesmo os que saíram de venda; quem já tem continua com o preço que assinou).

available é false sem cobrança no ar ou num time cobrado por parceiro. can_purchase diz se o time pode adicionar pacotes agora, e reason, quando não pode: no_paid_subscription, subscription_not_active, billed_by_partner ou billing_unavailable (null quando pode). currency e interval são os da assinatura. limit traz plan, packs, effective e scheduled (null, ou {"effective_at", "pack_contacts", "effective_limit"} quando há redução agendada). subscription é null sem assinatura de marketing paga. Em packs, amount é o preço de um pacote, em centavos, na moeda da assinatura (null se o pacote não é vendido nela), prices lista as moedas, purchasable diz se dá para aumentar a quantidade e quantity e scheduled_quantity são as do time.

Erros

  • 403forbidden
    Quem chamou não é dono nem admin do time, ou usou uma chave de API (estas rotas só aceitam a sessão do painel).
Requisição
GET /billing/contact-packs HTTP/1.1
Host: api.postal.so
200Resposta (valores ilustrativos)
{
  "object": "contact_packs",
  "available": true,
  "can_purchase": true,
  "reason": null,
  "currency": "brl",
  "interval": "month",
  "contacts_used": 3120,
  "limit": {
    "plan": 5000,
    "packs": 20000,
    "effective": 25000,
    "scheduled": null
  },
  "subscription": {
    "id": "019a2e11-6c7d-7e8f-9a01-3b4c5d6e7f80",
    "status": "active",
    "currency": "brl",
    "interval": "month",
    "amount": 5900,
    "current_period_end": "2026-11-20T12:00:00+00:00",
    "cancel_at_period_end": false
  },
  "packs": [
    {
      "object": "contact_pack",
      "id": "019a2e10-4b5c-7d6e-8f70-1a2b3c4d5e6f",
      "name": "Pacote A",
      "contacts": 10000,
      "max_quantity": 10,
      "amount": 4900,
      "currency": "brl",
      "interval": "month",
      "prices": [
        {
          "currency": "brl",
          "amount": 4900
        },
        {
          "currency": "usd",
          "amount": 900
        }
      ],
      "purchasable": true,
      "quantity": 2,
      "scheduled_quantity": null
    },
    {
      "object": "contact_pack",
      "id": "019a2e10-4b5d-7e7f-9a81-2b3c4d5e6f70",
      "name": "Pacote B",
      "contacts": 25000,
      "max_quantity": 10,
      "amount": 9900,
      "currency": "brl",
      "interval": "month",
      "prices": [
        {
          "currency": "brl",
          "amount": 9900
        },
        {
          "currency": "usd",
          "amount": 1900
        }
      ],
      "purchasable": true,
      "quantity": 0,
      "scheduled_quantity": null
    }
  ]
}

#Cotar uma quantidade de pacotes

POST/billing/contact-packs/quote

Sessão do painel (dono ou admin)

Mostra o que aconteceria com esta quantidade do pacote, sem mudar nada: é o que o painel pergunta antes do botão Confirmar. Uma mudança que não pode ser feita não é um erro: a resposta é 200 com change.allowed: false e o motivo em change.reason.

pack é o pacote, como na listagem, mas com quantity e scheduled_quantity zerados: as quantidades do time estão em current (quantity, scheduled_quantity e o limit de hoje). change.type é increase, decrease, keep (desiste de uma redução agendada) ou same, e change.effective é now ou next_period (com effective_at na redução). change.proration_amount é o valor cobrado agora num aumento, em centavos, proporcional ao resto do período (null fora de um aumento ou quando o cálculo não está disponível). change.reason (null quando permitido) é um destes: no_paid_subscription, subscription_not_active, billed_by_partner, billing_unavailable, pack_unavailable, currency_mismatch, interval_mismatch, max_quantity, same_quantity, subscription_ending ou contacts_above_limit (com os blockers, no formato do downgrade_blocked).

after traz limit_after_payment (o limite depois que o aumento for pago), limit_after_renewal (o limite depois da próxima renovação), pack_amount (este pacote vezes a quantidade, em centavos) e recurring_total (o que a assinatura passa a custar por período: plano mais pacotes). Os valores em dinheiro são null quando não dá para calcular.

Corpo da requisição

  • pack_idstringobrigatório
    O ID do pacote, como em packs[].id de GET /billing/contact-packs.
  • quantityintegerobrigatório
    A quantidade total que o time quer ter deste pacote, de 0 a 1000 (não é quanto somar). Pode ser maior (aumento), menor ou 0 (redução).

Erros

  • 403forbidden
    Quem chamou não é dono nem admin do time, ou usou uma chave de API (estas rotas só aceitam a sessão do painel).
  • 422validation_error
    pack_id inexistente ou quantity que não é um inteiro de 0 a 1000.
Requisição
POST /billing/contact-packs/quote HTTP/1.1
Host: api.postal.so
Content-Type: application/json

{
  "pack_id": "019a2e10-4b5c-7d6e-8f70-1a2b3c4d5e6f",
  "quantity": 3
}
200Resposta (um aumento de 1 para 3; valores ilustrativos)
{
  "object": "contact_pack_quote",
  "pack": {
    "object": "contact_pack",
    "id": "019a2e10-4b5c-7d6e-8f70-1a2b3c4d5e6f",
    "name": "Pacote A",
    "contacts": 10000,
    "max_quantity": 10,
    "amount": 4900,
    "currency": "brl",
    "interval": "month",
    "prices": [
      {
        "currency": "brl",
        "amount": 4900
      },
      {
        "currency": "usd",
        "amount": 900
      }
    ],
    "purchasable": true,
    "quantity": 0,
    "scheduled_quantity": null
  },
  "quantity": 3,
  "currency": "brl",
  "interval": "month",
  "current": {
    "quantity": 1,
    "scheduled_quantity": null,
    "limit": 15000
  },
  "change": {
    "type": "increase",
    "effective": "now",
    "effective_at": null,
    "proration_amount": 4000,
    "allowed": true,
    "reason": null,
    "blockers": []
  },
  "after": {
    "limit_after_payment": 35000,
    "limit_after_renewal": 35000,
    "pack_amount": 14700,
    "recurring_total": 20600
  }
}
200Uma redução recusada: o time tem mais contatos do que o limite que sobraria
{
  "object": "contact_pack_quote",
  "pack": {
    "object": "contact_pack",
    "id": "019a2e10-4b5c-7d6e-8f70-1a2b3c4d5e6f",
    "name": "Pacote A",
    "contacts": 10000,
    "max_quantity": 10,
    "amount": 4900,
    "currency": "brl",
    "interval": "month",
    "prices": [
      {
        "currency": "brl",
        "amount": 4900
      },
      {
        "currency": "usd",
        "amount": 900
      }
    ],
    "purchasable": true,
    "quantity": 0,
    "scheduled_quantity": null
  },
  "quantity": 0,
  "currency": "brl",
  "interval": "month",
  "current": {
    "quantity": 3,
    "scheduled_quantity": null,
    "limit": 35000
  },
  "change": {
    "type": "decrease",
    "effective": "next_period",
    "effective_at": "2026-11-20T12:00:00+00:00",
    "proration_amount": null,
    "allowed": false,
    "reason": "contacts_above_limit",
    "blockers": [
      {
        "key": "contacts",
        "label": "Contatos",
        "type": "quantity",
        "used": 16000,
        "limit": 15000,
        "excess": 1000,
        "action": "manage",
        "manage_path": "/audience"
      }
    ]
  },
  "after": {
    "limit_after_payment": 35000,
    "limit_after_renewal": 5000,
    "pack_amount": 0,
    "recurring_total": 5900
  }
}

#Definir a quantidade de um pacote

PUT/billing/contact-packs

Sessão do painel (dono ou admin)

Define quantos deste pacote o time tem (0 remove). POST no mesmo caminho faz o mesmo. A quantidade é a total, e a resposta diz o que foi feito em action:

actionStatusO que significa
updated200O aumento foi cobrado e pago: o limite já vale. A resposta traz o contact_packs atualizado.
payment_pending202O pagamento não foi confirmado (o cartão pede confirmação, por exemplo). invoice_url é o link da fatura para pagar. O limite não muda até o pagamento ser confirmado: leia GET /billing/contact-packs de novo depois.
scheduled200Uma redução ou remoção ficou para a próxima renovação (effective_at), sem reembolso. O limite continua o mesmo até lá.
reduction_canceled200Você pediu a quantidade atual enquanto havia uma redução agendada, e ela foi desfeita.

A rota tem limite de requisições mais baixo que o resto da API, como as outras que mexem na cobrança. Antes de confirmar, use a cotação para mostrar o valor.

Corpo da requisição

  • pack_idstringobrigatório
    O ID do pacote, como em packs[].id de GET /billing/contact-packs.
  • quantityintegerobrigatório
    A quantidade total que o time quer ter deste pacote, de 0 a 1000 (não é quanto somar). 0 remove o pacote na próxima renovação.

Erros

  • 403forbidden
    Quem chamou não é dono nem admin do time, ou usou uma chave de API (estas rotas só aceitam a sessão do painel).
  • 403contact_packs_unavailable
    Sem plano de marketing pago, ou com a assinatura em teste ou com pagamento pendente.
  • 403billed_by_partner
    O time é cobrado por um parceiro, que muda os pacotes.
  • 422validation_error
    Pacote fora de venda, não vendido na moeda ou na cobrança da assinatura, quantidade acima do max_quantity, quantidade igual à atual ou assinatura que termina no fim do período. A mensagem diz qual.
  • 422payment_failed
    O cartão foi recusado. Nada mudou.
  • 422downgrade_blocked
    O time tem mais contatos do que o limite que sobraria. blockers diz quantos contatos passam.
  • 502stripe_unavailable
    O serviço de pagamento não respondeu como esperado. Tente de novo em instantes; nada mudou.
  • 503billing_unavailable
    A cobrança não está disponível agora.
Requisição
PUT /billing/contact-packs HTTP/1.1
Host: api.postal.so
Content-Type: application/json

{
  "pack_id": "019a2e10-4b5c-7d6e-8f70-1a2b3c4d5e6f",
  "quantity": 2
}
200Resposta (aumento pago; valores ilustrativos)
{
  "action": "updated",
  "contact_packs": {
    "object": "contact_packs",
    "available": true,
    "can_purchase": true,
    "reason": null,
    "currency": "brl",
    "interval": "month",
    "contacts_used": 3120,
    "limit": {
      "plan": 5000,
      "packs": 20000,
      "effective": 25000,
      "scheduled": null
    },
    "subscription": {
      "id": "019a2e11-6c7d-7e8f-9a01-3b4c5d6e7f80",
      "status": "active",
      "currency": "brl",
      "interval": "month",
      "amount": 5900,
      "current_period_end": "2026-11-20T12:00:00+00:00",
      "cancel_at_period_end": false
    },
    "packs": [
      {
        "object": "contact_pack",
        "id": "019a2e10-4b5c-7d6e-8f70-1a2b3c4d5e6f",
        "name": "Pacote A",
        "contacts": 10000,
        "max_quantity": 10,
        "amount": 4900,
        "currency": "brl",
        "interval": "month",
        "prices": [
          {
            "currency": "brl",
            "amount": 4900
          },
          {
            "currency": "usd",
            "amount": 900
          }
        ],
        "purchasable": true,
        "quantity": 2,
        "scheduled_quantity": null
      },
      {
        "object": "contact_pack",
        "id": "019a2e10-4b5d-7e7f-9a81-2b3c4d5e6f70",
        "name": "Pacote B",
        "contacts": 25000,
        "max_quantity": 10,
        "amount": 9900,
        "currency": "brl",
        "interval": "month",
        "prices": [
          {
            "currency": "brl",
            "amount": 9900
          },
          {
            "currency": "usd",
            "amount": 1900
          }
        ],
        "purchasable": true,
        "quantity": 0,
        "scheduled_quantity": null
      }
    ]
  }
}
202O pagamento precisa ser confirmado: o limite não mudou
{
  "action": "payment_pending",
  "invoice_url": "https://invoice.example.com/i/inv_abc123",
  "contact_packs": {
    "object": "contact_packs",
    "…": "…"
  }
}
200Uma redução para a próxima renovação
{
  "action": "scheduled",
  "effective_at": "2026-11-20T12:00:00+00:00",
  "contact_packs": {
    "object": "contact_packs",
    "…": "…"
  }
}
422Redução recusada
{
  "statusCode": 422,
  "name": "downgrade_blocked",
  "message": "Você tem mais contatos do que o limite que restaria ao reduzir este pacote. Remova contatos primeiro e depois reduza o pacote.",
  "blockers": [
    {
      "key": "contacts",
      "label": "Contatos",
      "type": "quantity",
      "used": 16000,
      "limit": 15000,
      "excess": 1000,
      "action": "manage",
      "manage_path": "/audience"
    }
  ]
}