Iniciar sesión Crear cuenta
Referencia de la API

Planes

La lista pública de planes, precios y límites y, para el propietario y los administradores, el Adicional de envíos del plan de envío y el Adicional de contactos del plan de marketing.

La lista pública de planes, la misma que usa la página de precios. No necesita clave de API. Más abajo, el Adicional de envíos del plan de envío y el Adicional de contactos del plan de marketing, que el propietario y los administradores gestionan con la sesión del panel.

#Listar planes

GET/plans

Público, sin clave

Planes activos y públicos, en el orden de visualización, de los dos productos: kind es transactional (envío) o marketing (contactos y campañas). Los precios en prices están en centavos, por moneda (brl, usd y eur) e intervalo (month, year); overage_prices es el precio por mil por encima del límite, cuando el plan lo permite (allows_overage): correos electrónicos por encima de la cuota mensual en los planes de envío y contactos por encima del límite en los planes de marketing (overage_unit indica cuál). En los limits, null es ilimitado, 0 es no incluido y rate_limit es el número de solicitudes por segundo. La respuesta también trae el feature_catalog, y el idioma de los textos sigue el encabezado Accept-Language (pt-BR o en); sin él, los textos vienen en portugués.

Límites y funciones. limits trae todas las claves del producto del plan, ya resueltas: números (null ilimitado, 0 no incluido), funciones de activar/desactivar (1 incluida, 0 no; postal_branding está invertido: 1 es tener el sello) y funciones informativas. El feature_catalog describe cada clave: sections, cada una con key, label y features, y cada función con key, label, description, type (number o boolean), unit y unit_one (plural y singular, solo en los números), inverted (solo cuando es verdadero), kind (transactional, marketing o both) y enforced (la API ya rechaza lo que el plan no incluye). Consulta qué bloquea cada clave en Límites y funciones del plan.

Rangos definidos por Postal. La escala de volumen del panel se compone de rangos que Postal registra por producto (volume_bands: {"transactional": [{"id", "volume"}], "marketing": [...]}, en el orden registrado; vacío mientras el producto no tiene rangos). Cada plan indica cuáles cubre: bands lista los volúmenes de los rangos que cubre, en orden creciente, y cada precio en prices trae el volume_band_id del rango al que corresponde (o null). Un plan que no cubre un rango simplemente no vende ese volumen; por encima del último rango solo lo cubre el plan a medida. El volume de un precio vinculado a un rango es el del rango. Sin rangos registrados, vale lo que se describe a continuación.

Rangos de volumen. Un plan puede tener varios precios por moneda e intervalo, uno para cada rango: volume es el volumen mensual del rango (correos electrónicos en el plan de envío, contactos en el de marketing; volume_unit indica cuál) y vale en lugar de emails_per_month o contacts de limits para quien contrata ese rango. emails_per_month, dentro del precio, repite volume en los planes de envío. overage_tiers lista el precio del excedente de cada rango, y overage_prices resume el precio por moneda. El equipo activa el excedente de cada producto en el panel; cada paquete de 1.000 iniciado entra en la próxima factura. purchasable es false en un plan o precio que no se puede comprar (no está vinculado a Stripe), e is_default marca el plan con el que empieza todo equipo. highlights es lo que destaca la tarjeta de precios del sitio: null (formato compacto predeterminado) o {"inherit", "items"}, con inherit abriendo la tarjeta con “Todo lo del plan anterior, más:” e items en orden, cada uno {"type": "feature", "key"} o {"type": "text", "text"}.

Adicional de contactos. contact_packs lista los paquetes del Adicional de contactos que están a la venta para los planes de marketing de pago: [{"object": "contact_pack", "id", "name", "contacts", "max_quantity", "interval", "prices": [{"currency", "amount"}]}], con los precios en centavos. La lista viene vacía mientras no haya ninguno a la venta. Mira Adicional de contactos.

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);
200Respuesta (resumida, con 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 envíos

Cuando el plan de envío del equipo tiene un límite mensual y ofrece el Adicional de envíos, el propietario o un administrador compra paquetes de envíos extra, con un pago único (no es una suscripción). Los créditos forman una reserva, aparte de la cuota del plan, y solo se usan después de agotada la cuota mensual. La guía del panel muestra el flujo en pantalla: Adicional de envíos.

Solo con la sesión del panel

Las dos rutas de abajo usan la sesión del panel de un propietario o administrador del equipo (GET /billing/email-credits también responde a otros miembros, pero sin las compras y sin poder comprar). Una clave de API, de acceso total o solo de envío, recibe 403 en la compra. Por eso los ejemplos muestran la solicitud como la envía el panel, sin encabezado de clave. Para leer la reserva con una clave, usa emails.credits de GET /usage.

  • Orden de uso. La cuota mensual del plan, después la reserva (los créditos que vencen antes primero, los que no vencen al final), después el excedente, si el equipo lo activó (solo se cobran los correos que la reserva no cubrió), y entonces el rechazo: 429 monthly_quota_exceeded.
  • Un crédito por correo, contado como lo cuenta la cuota. Los correos a las direcciones de prueba no gastan crédito. Un lote que la reserva no cubre (sin excedente activado) se rechaza completo, sin gastar crédito.
  • Las campañas no usan la reserva, pero cuentan en la misma cuota mensual.
  • El límite diario del plan se sigue aplicando: la reserva no lo levanta, y el envío recibe 429 daily_quota_exceeded.
  • Vigencia. Cada paquete tiene la suya, en días (validity_days, contados desde la confirmación del pago), o ninguna: con validity_days: 0, expires_at es null y los créditos no vencen.
  • Reembolso. Reembolsar el pago retira solo los créditos que aún no se usaron; lo que ya salió de la reserva sigue usado.
  • Quién puede comprar. Solo el propietario y los administradores. Un plan sin límite mensual no tiene nada que ampliar: 403 plan_limit_reached. Equipo facturado por un socio: 403 billed_by_partner.
  • Pago. Tarjeta y, cuando los precios del paquete están en reales y el Pix está activado, Pix (payment_methods). Con Pix, los créditos llegan cuando se confirma el pago.

Dónde aparece la reserva en las respuestas

RutaCampoQué trae
GET /usageemails.creditsLa reserva junto a la cuota: emails.monthly sigue con el used y el limit del plan. Las claves solo de envío ven el saldo, pero no la lista grants.
GET /billingemails.monthly, email_creditsEl resumen del equipo, solo con la sesión del panel (propietario o administrador): mira la tabla de abajo.
GET /billing/email-creditstoda la respuestaLa vista de la sección Adicional de envíos: la reserva, los paquetes a la venta y las compras.

En GET /billing, la cuota del plan y la reserva vienen por separado:

CampoQué dice
emails.monthlyLa cuota mensual del plan: used, limit (null: sin límite) y resets_at. Los créditos nunca se suman a ella.
email_creditsLa reserva, con el mismo formato de balance en GET /billing/email-credits: reserve, balance, used_this_month, in_use, next_expires_at y grants.

#Ver el Adicional de envíos del equipo

GET/billing/email-credits

Sesión del panel (propietario o administrador)

Lo que muestra la sección Adicional de envíos: la reserva, los paquetes a la venta y, para el propietario y los administradores, el historial de compras.

available es false cuando la facturación no está disponible. included dice si el plan tiene un límite mensual de envíos que la reserva puede ampliar (false en un plan ilimitado; entonces packs viene vacío). can_purchase es true para el propietario y los administradores de un equipo facturado por Postal, en un plan con límite mensual. payment_methods lista card y, cuando el Pix está activado, pix.

balance es la reserva: balance (los créditos que se pueden usar ahora), used_this_month, in_use (true cuando la cuota mensual se agotó y hay saldo), next_expires_at (el primer vencimiento, o null) y grants, los paquetes y regalos con saldo en el orden en que se usan; expires_at: null es un paquete sin vencimiento. packs son los paquetes a la venta: emails, amount (centavos, en la moneda principal currency), prices (un precio por moneda), validity_days (0: sin vencimiento) y popular. purchases trae las 50 compras y regalos más recientes (vacío para quien no es propietario ni administrador): status es pending (Pix aún sin pagar), active, depleted, expired, failed, refunded o revoked; source es purchase o staff (un regalo); emails es lo que vino en el paquete y remaining lo que queda; receipt_url es el recibo, cuando lo hay.

Errores

  • 403invalid_access
    No hay ningún equipo seleccionado en la sesión.
Solicitud
GET /billing/email-credits HTTP/1.1
Host: api.postal.so
200Respuesta (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 un paquete del Adicional de envíos

POST/billing/email-credits/checkout

Sesión del panel (propietario o administrador)

Crea la compra de un paquete y devuelve la dirección de la página de pago (url): lleva a la persona hasta ella. La compra todavía no existe como crédito: los envíos entran en la reserva cuando se confirma el pago (de inmediato con tarjeta; con Pix, cuando se paga). El pago es único y usa la moneda de facturación del equipo cuando el paquete tiene precio en ella; si no, la moneda principal del paquete.

Al terminar o desistir, el navegador vuelve a Configuración › Facturación del panel con ?extra_sends=success (pago recibido) o ?extra_sends=canceled (compra cancelada, sin cobro). Para saber si los créditos ya entraron, lee de nuevo GET /billing/email-credits: la compra aparece en purchases como pending y pasa a active. La ruta tiene un límite de solicitudes más bajo que el resto de la API, como las demás que tocan la facturación.

Cuerpo de la solicitud

Errores

  • 403forbidden
    Quien llamó no es propietario ni administrador del equipo, o usó una clave de API (estas rutas solo aceptan la sesión del panel).
  • 403billed_by_partner
    El equipo lo factura un socio, que gestiona el adicional.
  • 403plan_limit_reached
    El plan no tiene límite mensual de envíos, así que no hay nada que ampliar.
  • 422validation_error
    pack_id ausente, que no es un UUID o no existe, o un paquete que ya no está a la venta (el mensaje dice cuál).
  • 503billing_unavailable
    La facturación no está disponible ahora.
Solicitud
POST /billing/email-credits/checkout HTTP/1.1
Host: api.postal.so
Content-Type: application/json

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

#Adicional de contactos

Cuando el plan de marketing del equipo ofrece el Adicional de contactos, el propietario o un administrador suma contactos al límite sin cambiar de plan. Cada paquete tiene un precio recurrente, que se suma al de la suscripción de marketing, y el límite de contactos pasa a ser contatos do plano + contatos de cada pacote × quantidade. Los paquetes solo existen para un plan de marketing de pago y activo, y solo los que Postal puso a la venta: GET /plans trae los que están a la venta en contact_packs (vacío cuando no hay ninguno). La guía del panel muestra el flujo en pantalla: Adicional de contactos.

Solo con la sesión del panel

Las tres rutas de abajo usan la sesión del panel de un propietario o administrador del equipo. Una clave de API, de acceso total o solo de envío, recibe 403, y los demás miembros del equipo también. Por eso los ejemplos muestran la petición como la envía el panel, sin cabecera de clave. Para leer el límite con los paquetes usando una clave, usa GET /usage.

  • Aumentar. Se cobra al momento, de forma proporcional al resto del período, y después en cada renovación, en la misma factura de la suscripción. El límite solo crece después de que se confirme el pago: confirmado al momento, la respuesta es 200 con action: "updated"; si el pago necesita confirmación (la autenticación de la tarjeta, por ejemplo), es 202 con action: "payment_pending" y el invoice_url de la factura por pagar, y el límite no cambia hasta que se pague. Pago rechazado: 422 payment_failed, y no cambia nada.
  • Reducir o quitar. Vale en la próxima renovación, sin reembolso, y el límite sigue igual hasta entonces (action: "scheduled"). Pedir de nuevo la cantidad actual desiste de la reducción programada (action: "reduction_canceled"). La reducción se rechaza con 422 downgrade_blocked (y la lista blockers) si el equipo tiene más contactos que el límite que quedaría, y también si la suscripción termina al final del período.
  • Varios del mismo paquete. Se puede tener más de uno, hasta el max_quantity del paquete por equipo.
  • Quién puede. Solo con plan de marketing de pago y suscripción activa. Sin suscripción de pago (plan Gratis), o con la suscripción en prueba o con pago pendiente: 403 contact_packs_unavailable. Equipo facturado por un socio: 403 billed_by_partner. La moneda y el cobro (mensual o anual) del paquete tienen que ser los de la suscripción.
  • Plan y suscripción. Los paquetes se mantienen cuando el plan cambia (mejora o cambio programado) y se suman al límite del plan nuevo. Terminan junto con la suscripción, y el equipo vuelve al límite del plan en el que queda. Si el límite de contactos del equipo se ajustó de forma individual, ese número vale como el final y los paquetes no se suman a él.

Dónde aparecen los paquetes en las respuestas

RutaCampoQué trae
GET /planscontact_packsLos paquetes a la venta, sin las cantidades del equipo: object, id, name, contacts (cuántos contactos suma cada uno), max_quantity, interval y prices ([{"currency", "amount"}], en centavos).
GET /usagecontactslimit es el límite vigente (plan + paquetes); plan_limit, solo el del plan; packs trae contacts (lo que suman los paquetes), effective, scheduled y, para claves de acceso total, items. Las claves solo de envío ven los totales, pero no qué paquetes tiene el equipo.
GET /billingcontact_packsEl resumen del equipo, solo con la sesión del panel (propietario o administrador): mira la tabla de abajo.
GET /billing/contact-packstoda la respuestaLa vista de la pantalla Adicional de contactos: el límite, la suscripción y los paquetes a la venta.

El resumen del equipo (contact_packs de GET /billing) tiene estos campos:

CampoQué dice
plan_limitLos contactos del plan (null: sin límite).
pack_contactsLos contactos que suman los paquetes ahora.
effective_limitEl límite vigente ahora: plan_limit + pack_contacts (null: sin límite).
itemsLos paquetes que tiene el equipo: pack_id, name, contacts, quantity, scheduled_quantity (la cantidad después de la renovación, o null sin reducción programada), contacts_total (contacts × quantity), max_quantity, amount (centavos, por unidad), currency e interval.
schedulednull, o la reducción que espera la renovación: effective_at, pack_contacts y effective_limit (el límite después de ella).

#Ver los paquetes de contactos del equipo

GET/billing/contact-packs

Sesión del panel (propietario o administrador)

Lo que muestra la pantalla Adicional de contactos: el límite, la suscripción de marketing y los paquetes a la venta en la moneda y el cobro de la suscripción, más los que el equipo ya tiene (incluso los que salieron de venta; quien ya tiene uno conserva el precio que contrató).

available es false sin facturación disponible o en un equipo facturado por un socio. can_purchase dice si el equipo puede añadir paquetes ahora, y reason, cuando no puede: no_paid_subscription, subscription_not_active, billed_by_partner o billing_unavailable (null cuando puede). currency e interval son los de la suscripción. limit trae plan, packs, effective y scheduled (null, o {"effective_at", "pack_contacts", "effective_limit"} cuando hay una reducción programada). subscription es null sin suscripción de marketing de pago. En packs, amount es el precio de un paquete, en centavos, en la moneda de la suscripción (null si el paquete no se vende en ella), prices lista las monedas, purchasable dice si se puede aumentar la cantidad y quantity y scheduled_quantity son las del equipo.

Errores

  • 403forbidden
    Quien llamó no es propietario ni administrador del equipo, o usó una clave de API (estas rutas solo aceptan la sesión del panel).
Solicitud
GET /billing/contact-packs HTTP/1.1
Host: api.postal.so
200Respuesta (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
    }
  ]
}

#Cotizar una cantidad de paquetes

POST/billing/contact-packs/quote

Sesión del panel (propietario o administrador)

Muestra qué pasaría con esta cantidad del paquete, sin cambiar nada: es lo que el panel pregunta antes del botón Confirmar. Un cambio que no se puede hacer no es un error: la respuesta es 200 con change.allowed: false y el motivo en change.reason.

pack es el paquete, como en el listado, pero con quantity y scheduled_quantity en cero: las cantidades del equipo están en current (quantity, scheduled_quantity y el limit de hoy). change.type es increase, decrease, keep (desiste de una reducción programada) o same, y change.effective es now o next_period (con effective_at en la reducción). change.proration_amount es el importe que se cobra ahora en un aumento, en centavos, proporcional al resto del período (null fuera de un aumento o cuando el cálculo no está disponible). change.reason (null cuando está permitido) es uno de estos: no_paid_subscription, subscription_not_active, billed_by_partner, billing_unavailable, pack_unavailable, currency_mismatch, interval_mismatch, max_quantity, same_quantity, subscription_ending o contacts_above_limit (con los blockers, en el formato de downgrade_blocked).

after trae limit_after_payment (el límite una vez pagado el aumento), limit_after_renewal (el límite después de la próxima renovación), pack_amount (este paquete por la cantidad, en centavos) y recurring_total (lo que pasa a costar la suscripción por período: plan más paquetes). Los valores de dinero son null cuando no se pueden calcular.

Cuerpo de la solicitud

  • pack_idstringobligatorio
    El ID del paquete, como en packs[].id de GET /billing/contact-packs.
  • quantityintegerobligatorio
    La cantidad total que el equipo quiere tener de este paquete, de 0 a 1000 (no es cuánto sumar). Puede ser mayor (aumento), menor o 0 (reducción).

Errores

  • 403forbidden
    Quien llamó no es propietario ni administrador del equipo, o usó una clave de API (estas rutas solo aceptan la sesión del panel).
  • 422validation_error
    pack_id inexistente, o quantity que no es un entero de 0 a 1000.
Solicitud
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
}
200Respuesta (un aumento de 1 a 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
  }
}
200Una reducción rechazada: el equipo tiene más contactos que el límite que quedaría
{
  "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 la cantidad de un paquete

PUT/billing/contact-packs

Sesión del panel (propietario o administrador)

Define cuántos de este paquete tiene el equipo (0 lo quita). POST en la misma ruta hace lo mismo. La cantidad es la total, y la respuesta dice qué se hizo en action:

actionEstadoQué significa
updated200El aumento se cobró y se pagó: el límite ya vale. La respuesta trae el contact_packs actualizado.
payment_pending202El pago no se confirmó (la tarjeta pide confirmación, por ejemplo). invoice_url es el enlace de la factura por pagar. El límite no cambia hasta que se confirme el pago: lee GET /billing/contact-packs de nuevo después.
scheduled200Una reducción o eliminación quedó para la próxima renovación (effective_at), sin reembolso. El límite sigue igual hasta entonces.
reduction_canceled200Pediste la cantidad actual mientras había una reducción programada, y se deshizo.

La ruta tiene un límite de peticiones más bajo que el resto de la API, como las otras que tocan la facturación. Antes de confirmar, usa la cotización para mostrar el importe.

Cuerpo de la solicitud

  • pack_idstringobligatorio
    El ID del paquete, como en packs[].id de GET /billing/contact-packs.
  • quantityintegerobligatorio
    La cantidad total que el equipo quiere tener de este paquete, de 0 a 1000 (no es cuánto sumar). 0 quita el paquete en la próxima renovación.

Errores

  • 403forbidden
    Quien llamó no es propietario ni administrador del equipo, o usó una clave de API (estas rutas solo aceptan la sesión del panel).
  • 403contact_packs_unavailable
    Sin plan de marketing de pago, o con la suscripción en prueba o con pago pendiente.
  • 403billed_by_partner
    El equipo lo factura un socio, que cambia los paquetes.
  • 422validation_error
    Paquete fuera de venta, no vendido en la moneda o el cobro de la suscripción, cantidad por encima del max_quantity, cantidad igual a la actual o suscripción que termina al final del período. El mensaje dice cuál.
  • 422payment_failed
    La tarjeta se rechazó. No cambió nada.
  • 422downgrade_blocked
    El equipo tiene más contactos que el límite que quedaría. blockers dice por cuántos contactos se pasa.
  • 502stripe_unavailable
    El servicio de pago no respondió como se esperaba. Inténtalo de nuevo en un momento; no cambió nada.
  • 503billing_unavailable
    La facturación no está disponible ahora.
Solicitud
PUT /billing/contact-packs HTTP/1.1
Host: api.postal.so
Content-Type: application/json

{
  "pack_id": "019a2e10-4b5c-7d6e-8f70-1a2b3c4d5e6f",
  "quantity": 2
}
200Respuesta (aumento pagado; 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
      }
    ]
  }
}
202El pago hay que confirmarlo: el límite no cambió
{
  "action": "payment_pending",
  "invoice_url": "https://invoice.example.com/i/inv_abc123",
  "contact_packs": {
    "object": "contact_packs",
    "…": "…"
  }
}
200Una reducción para la próxima renovación
{
  "action": "scheduled",
  "effective_at": "2026-11-20T12:00:00+00:00",
  "contact_packs": {
    "object": "contact_packs",
    "…": "…"
  }
}
422Reducción rechazada
{
  "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"
    }
  ]
}