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
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.
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);
{
"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: convalidity_days: 0,expires_atesnully 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:
403plan_limit_reached. Equipo facturado por un socio:403billed_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
| Ruta | Campo | Qué trae |
|---|---|---|
GET /usage | emails.credits | La 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 /billing | emails.monthly, email_credits | El resumen del equipo, solo con la sesión del panel (propietario o administrador): mira la tabla de abajo. |
GET /billing/email-credits | toda la respuesta | La 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:
| Campo | Qué dice |
|---|---|
emails.monthly | La cuota mensual del plan: used, limit (null: sin límite) y resets_at. Los créditos nunca se suman a ella. |
email_credits | La 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
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
- 403
invalid_accessNo hay ningún equipo seleccionado en la sesión.
GET /billing/email-credits HTTP/1.1
Host: api.postal.so
{
"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
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
pack_idstringobligatorioEl ID del paquete, como enpacks[].iddeGET /billing/email-credits.
Errores
- 403
forbiddenQuien llamó no es propietario ni administrador del equipo, o usó una clave de API (estas rutas solo aceptan la sesión del panel). - 403
billed_by_partnerEl equipo lo factura un socio, que gestiona el adicional. - 403
plan_limit_reachedEl plan no tiene límite mensual de envíos, así que no hay nada que ampliar. - 422
validation_errorpack_idausente, que no es un UUID o no existe, o un paquete que ya no está a la venta (el mensaje dice cuál). - 503
billing_unavailableLa facturación no está disponible ahora.
POST /billing/email-credits/checkout HTTP/1.1
Host: api.postal.so
Content-Type: application/json
{
"pack_id": "019a3f20-5b6c-7d7e-8f90-1a2b3c4d5e6f"
}
{
"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
200conaction: "updated"; si el pago necesita confirmación (la autenticación de la tarjeta, por ejemplo), es202conaction: "payment_pending"y elinvoice_urlde la factura por pagar, y el límite no cambia hasta que se pague. Pago rechazado:422payment_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 con422downgrade_blocked(y la listablockers) 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_quantitydel 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:
403contact_packs_unavailable. Equipo facturado por un socio:403billed_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
| Ruta | Campo | Qué trae |
|---|---|---|
GET /plans | contact_packs | Los 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 /usage | contacts | limit 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 /billing | contact_packs | El resumen del equipo, solo con la sesión del panel (propietario o administrador): mira la tabla de abajo. |
GET /billing/contact-packs | toda la respuesta | La 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:
| Campo | Qué dice |
|---|---|
plan_limit | Los contactos del plan (null: sin límite). |
pack_contacts | Los contactos que suman los paquetes ahora. |
effective_limit | El límite vigente ahora: plan_limit + pack_contacts (null: sin límite). |
items | Los 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. |
scheduled | null, 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
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
- 403
forbiddenQuien llamó no es propietario ni administrador del equipo, o usó una clave de API (estas rutas solo aceptan la sesión del panel).
GET /billing/contact-packs HTTP/1.1
Host: api.postal.so
{
"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
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_idstringobligatorioEl ID del paquete, como enpacks[].iddeGET /billing/contact-packs.quantityintegerobligatorioLa cantidad total que el equipo quiere tener de este paquete, de 0 a 1000 (no es cuánto sumar). Puede ser mayor (aumento), menor o0(reducción).
Errores
- 403
forbiddenQuien llamó no es propietario ni administrador del equipo, o usó una clave de API (estas rutas solo aceptan la sesión del panel). - 422
validation_errorpack_idinexistente, oquantityque no es un entero de 0 a 1000.
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
}
{
"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
}
}
{
"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
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:
| action | Estado | Qué significa |
|---|---|---|
updated | 200 | El aumento se cobró y se pagó: el límite ya vale. La respuesta trae el contact_packs actualizado. |
payment_pending | 202 | El 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. |
scheduled | 200 | Una reducción o eliminación quedó para la próxima renovación (effective_at), sin reembolso. El límite sigue igual hasta entonces. |
reduction_canceled | 200 | Pediste 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_idstringobligatorioEl ID del paquete, como enpacks[].iddeGET /billing/contact-packs.quantityintegerobligatorioLa cantidad total que el equipo quiere tener de este paquete, de 0 a 1000 (no es cuánto sumar).0quita el paquete en la próxima renovación.
Errores
- 403
forbiddenQuien llamó no es propietario ni administrador del equipo, o usó una clave de API (estas rutas solo aceptan la sesión del panel). - 403
contact_packs_unavailableSin plan de marketing de pago, o con la suscripción en prueba o con pago pendiente. - 403
billed_by_partnerEl equipo lo factura un socio, que cambia los paquetes. - 422
validation_errorPaquete fuera de venta, no vendido en la moneda o el cobro de la suscripción, cantidad por encima delmax_quantity, cantidad igual a la actual o suscripción que termina al final del período. El mensaje dice cuál. - 422
payment_failedLa tarjeta se rechazó. No cambió nada. - 422
downgrade_blockedEl equipo tiene más contactos que el límite que quedaría.blockersdice por cuántos contactos se pasa. - 502
stripe_unavailableEl servicio de pago no respondió como se esperaba. Inténtalo de nuevo en un momento; no cambió nada. - 503
billing_unavailableLa facturación no está disponible ahora.
PUT /billing/contact-packs HTTP/1.1
Host: api.postal.so
Content-Type: application/json
{
"pack_id": "019a2e10-4b5c-7d6e-8f70-1a2b3c4d5e6f",
"quantity": 2
}
{
"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
}
]
}
}
{
"action": "payment_pending",
"invoice_url": "https://invoice.example.com/i/inv_abc123",
"contact_packs": {
"object": "contact_packs",
"…": "…"
}
}
{
"action": "scheduled",
"effective_at": "2026-11-20T12:00:00+00:00",
"contact_packs": {
"object": "contact_packs",
"…": "…"
}
}
{
"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"
}
]
}