Skip to content

Suscripciones (API) ​

Las suscripciones cobran de forma recurrente a un cliente vaulteado por uno o más productos recurrentes ("items"), cada uno con una cantidad. Cada ciclo la factura es Σ(product.price × quantity) sobre los items, con los precios del catálogo vigentes al momento de facturar — editar el precio de un producto en el dashboard cambia la próxima factura, no la ya emitida.

Todos los endpoints requieren tu API key de proyecto en el header X-API-Key.

Suscripciones de un proyecto con desglose de items y total por ciclo

Crear una suscripción ​

Suscribe a un cliente vaulteado a uno o más productos recurrentes (items), cada uno con una cantidad.

Modelo de facturación — no se cobra al crear

Crear una suscripción no cobra al cliente. El primer período comienza de inmediato y el primer cargo recurrente se ejecuta en currentPeriodEnd (un ciclo completo después), y luego uno en cada frontera de ciclo.

El flujo esperado es que el cliente pague por — y vaultee su tarjeta durante — el primer período fuera de banda, por ejemplo con un que cobra amount y almacena la tarjeta en el mismo checkout. La suscripción luego mueve las renovaciones a partir del período siguiente. Una suscripción creada sobre una tarjeta que nunca fue cobrada significa que su primera renovación es la primera vez que se ejercita la tarjeta — no hay validación de tarjeta al momento de crear.

Así es como un software con precios dinámicos por inquilino compone su total mensual desde un catálogo fijo del dashboard en lugar de crear productos desechables. Ejemplo: un plan POS de $35 base + $20 por cada sucursal adicional + $5 por cada usuario adicional, para un inquilino con 1 sucursal adicional y 2 usuarios adicionales:

items: [
  { "productCode": "prod_PLANBASE01", "quantity": 1 },  // plan base, $35.00
  { "productCode": "prod_SUCURSAL01", "quantity": 1 },  // sucursal adicional, $20.00
  { "productCode": "prod_USUARIO001", "quantity": 2 }   // usuario adicional, 2 × $5.00
]                                      → factura $65.00 / mes
http
POST /v1/subscriptions

Cuerpo de la solicitud:

CampoTipoRequeridoDescripción
customerIdstringSíCliente del proyecto con un método de pago almacenado
itemsarraySí*1–20 items: { productCode, quantity? }. quantity por defecto es 1 (máx 1000)
productCodestringSí*Forma legada de un solo producto — equivale a items: [{ productCode, quantity: 1 }]
isTestbooleanNoEntorno sandbox (por defecto false). Debe coincidir con el entorno en que se guardó la tarjeta del cliente: las renovaciones se cobran con las credenciales de ese entorno

* Envía o items o productCode, no ambos.

Reglas sobre el conjunto de items:

  • Todo producto debe ser recurring, estar activo y pertenecer al proyecto.
  • Todos los items deben compartir el mismo billingPeriod / billingInterval (un ciclo por suscripción) y la misma currency (una factura).
  • No se permiten productCodes duplicados — usa quantity en su lugar.
  • Un cliente puede tener varias suscripciones activas.
bash
curl -X POST https://api-pay.zelta.dev/v1/subscriptions \
  -H "X-API-Key: tu-api-key-de-proyecto" \
  -H "Content-Type: application/json" \
  -d '{
    "customerId": "7315555666677778888",
    "items": [
      { "productCode": "prod_PLANBASE01" },
      { "productCode": "prod_SUCURSAL01" },
      { "productCode": "prod_USUARIO001", "quantity": 2 }
    ]
  }'

Respuesta (201) (fragmento con los campos principales):

json
{
  "success": true,
  "data": {
    "subscription": {
      "id": "7316666777788889999",
      "customerId": "7315555666677778888",
      "status": "active",
      "isTest": false,
      "currentPeriodStart": "2026-06-04T00:00:00.000Z",
      "currentPeriodEnd": "2026-07-04T00:00:00.000Z",
      "nextBillingAt": "2026-07-04T00:00:00.000Z",
      "cancelAtPeriodEnd": false,
      "items": [
        { "productId": "7314444555566667777", "quantity": 1, "effectiveFrom": "2026-06-04T00:00:00.000Z" },
        { "productId": "7314444555566667778", "quantity": 1, "effectiveFrom": "2026-06-04T00:00:00.000Z" },
        { "productId": "7314444555566667779", "quantity": 2, "effectiveFrom": "2026-06-04T00:00:00.000Z" }
      ],
      "cycleTotal": 6500
    }
  },
  "message": "Subscription created",
  "timestamp": "2026-06-04T00:00:00.000Z"
}

Se emite un webhook subscription.created con los items (productCode, name, price, quantity) y el cycleTotal. Las facturas recurrentes luego emiten invoice.paid / invoice.payment_failed con el mismo desglose de items.

Cada renovación exitosa también emite charge.succeeded (con el total de la factura, cantidad 1 y el productCode del primer item) y receipt.created.

Consultar suscripciones ​

http
GET /v1/subscriptions
GET /v1/subscriptions/:id

GET /v1/subscriptions/:id siempre devuelve los items configurados (cada uno con su product anidado) más cycleTotal, junto con el producto principal (product) y el cliente (customer). En GET /v1/subscriptions agrega expand=items para incluir los items y cycleTotal (también acepta expand=customer y expand=product). El listado admite paginación estándar (lim de 1 a 100, off) y los filtros opcionales status (active, past_due, cancelled) y customerId.

El customer de una suscripción es siempre un , igual que el de los endpoints de clientes: en GET /v1/subscriptions/:id, en las respuestas de PATCH /v1/subscriptions/:id/items y PATCH /v1/subscriptions/:id/customer, y en el listado con expand=customer. No incluye la referencia de la bóveda ni el BIN y el vencimiento de la tarjeta.

Los estados de una suscripción son active, past_due y cancelled.

Errores comunes:

CódigoHTTPSignificado
ERR_CUSTOMER_NOT_FOUND404El cliente no existe en el proyecto
ERR_SUBSCRIPTION_NOT_FOUND404La suscripción no existe en el proyecto
ERR_PRODUCT_NOT_FOUND404Un productCode no existe en el proyecto
ERR_PRODUCT_NOT_RECURRING400Un item es un producto one_time — usa cargos en su lugar
ERR_PRODUCT_NOT_ACTIVE400El producto de un item está inactivo o archivado
ERR_MIXED_BILLING_PERIODS400Items con distinto billingPeriod/billingInterval
ERR_MIXED_CURRENCIES400Items con distintas monedas
ERR_CUSTOMER_NO_VAULT400El cliente no tiene método de pago almacenado
ERR_INVALID_SUBSCRIPTION_TRANSITION400La operación no es válida para el estado actual (p. ej. cambiar items o cliente de una suscripción cancelada)

Un productCode duplicado, o enviar items y productCode a la vez, devuelve 400 con el formato de .

Actualizar los items de una suscripción ​

Reemplaza todo el conjunto de items de la suscripción (mismas reglas que al crear, y el nuevo conjunto debe conservar el billingPeriod/billingInterval actual — para cambiar la cadencia, cancela y crea una nueva suscripción).

http
PATCH /v1/subscriptions/:id/items
bash
curl -X PATCH https://api-pay.zelta.dev/v1/subscriptions/7316666777788889999/items \
  -H "X-API-Key: tu-api-key-de-proyecto" \
  -H "Content-Type: application/json" \
  -d '{
    "items": [
      { "productCode": "prod_PLANBASE01" },
      { "productCode": "prod_SUCURSAL01", "quantity": 2 },
      { "productCode": "prod_USUARIO001", "quantity": 2 }
    ]
  }'

Se cobra desde la próxima renovación

No se cobra nada al hacer el cambio. El nuevo conjunto se usa desde el próximo cobro recurrente (en currentPeriodEnd), que ya factura la nueva composición completa; el webhook lleva effectiveAt con esa fecha. GET /v1/subscriptions/:id muestra el nuevo conjunto y su cycleTotal de inmediato. Para el dinero que el cliente debe ahora mismo (p. ej. un addon activado a mitad de período), combina el PATCH con un cargo único usando .

Se permite mientras la suscripción esté active o past_due. Responde con el mensaje "Subscription items updated" y la suscripción actualizada (con sus items, cycleTotal y customer, como en GET /v1/subscriptions/:id), y se emite un webhook subscription.items_updated con los nuevos items, cycleTotal y effectiveAt.

deferredAmountCents — solo para montos menores a $1 en NMI

El campo opcional deferredAmountCents existe para un caso específico: necesitas cobrar un monto de addon menor a $1.00 (menos de 100¢; por ejemplo, el resultado de una proración) y la tarjeta del cliente está guardada en NMI, que no procesa cargos por debajo de $1.00. Como ese monto no puede cobrarse por sí solo, esto lo suma a la próxima factura de la suscripción como un recargo de un ciclo sobre el primer item:

bash
curl -X PATCH https://api-pay.zelta.dev/v1/subscriptions/7316666777788889999/items \
  -H "X-API-Key: tu-api-key-de-proyecto" \
  -H "Content-Type: application/json" \
  -d '{
    "items": [ { "productCode": "prod_PLANBASE01" } ],
    "deferredAmountCents": 83
  }'

Acepta 1–99 (centavos enteros), se acumula entre llamadas y se incluye en la próxima factura que se genere. Si otra llamada posterior cambia la cantidad del primer item antes de esa factura, el recargo pendiente se pierde. No lo uses en ningún otro caso — para cualquier monto ≥ $1.00, o con Emetec, cobra el addon directamente con .

Cambiar el cliente de la suscripción ​

Reapunta la suscripción a otro cliente vaulteado del mismo proyecto. Como cada cliente tiene exactamente una tarjeta almacenada, así es como el software cambia el método de pago activo de un plan recurrente sin cancelar + recrear (lo que movería el ancla de facturación) y sin cobrar nada.

http
PATCH /v1/subscriptions/:id/customer

Cuerpo de la solicitud:

CampoTipoRequeridoDescripción
customerIdstringSíCliente de reemplazo; debe pertenecer al proyecto y tener un método de pago almacenado
bash
curl -X PATCH https://api-pay.zelta.dev/v1/subscriptions/7316666777788889999/customer \
  -H "X-API-Key: tu-api-key-de-proyecto" \
  -H "Content-Type: application/json" \
  -d '{ "customerId": "7319999000011112222" }'

Efectivo de inmediato, sin mover el ancla

Todo cargo futuro — la próxima renovación y cualquier reintento de dunning ya programado — factura al vault del nuevo cliente (el cliente se resuelve al momento del cargo). El período en curso, currentPeriodEnd y nextBillingAt no se mueven, así que nada se factura al momento del cambio.

Se permite mientras la suscripción esté active o past_due (este último es el caso de rescate de dunning: cambia a una tarjeta que funcione y deja que los reintentos recuperen el ciclo). Responde con el mensaje "Subscription customer updated" y la suscripción con el nuevo cliente en customer, y se emite un webhook subscription.customer_updated con el id del cliente nuevo y del anterior (si el cliente no cambia, no se emite). Falla con ERR_CUSTOMER_NO_VAULT si el cliente de reemplazo no tiene método de pago almacenado.

Cancelar una suscripción ​

Cancela una suscripción de inmediato o al final del período actual.

http
PATCH /v1/subscriptions/:id/cancel

Cuerpo de la solicitud:

CampoTipoRequeridoDescripción
cancelAtPeriodEndbooleanNofalse (por defecto) cancela de inmediato; true programa la cancelación para currentPeriodEnd
bash
curl -X PATCH https://api-pay.zelta.dev/v1/subscriptions/7316666777788889999/cancel \
  -H "X-API-Key: tu-api-key-de-proyecto" \
  -H "Content-Type: application/json" \
  -d '{ "cancelAtPeriodEnd": true }'
  • Inmediata (cancelAtPeriodEnd: false / omitido): la suscripción pasa a cancelled de inmediato y nextBillingAt se limpia. Emite subscription.cancelled con reason: "cancelled_by_request".
  • Al final del período (cancelAtPeriodEnd: true): la suscripción sigue en su estado actual hasta su próxima fecha de cobro (currentPeriodEnd, o el próximo reintento si está past_due) y en ese momento se cancela. Programar la cancelación no emite ningún webhook.

En ambos casos la respuesta lleva el mensaje "Subscription cancelled".

Cancelar al final del período nunca produce un cargo final

Cuando llega la fecha de cobro, una suscripción programada para cancelar no se factura: se cancela y emite subscription.cancelled con reason: "cancel_at_period_end".

Reactivar una suscripción ​

Deshace una cancelación, en cualquiera de sus dos formas:

  • Cancelación pendiente — la suscripción aún está active/past_due pero programada para cancelar al final del período (cancelAtPeriodEnd: true). Reactivar simplemente levanta esa cancelación programada; la facturación continúa intacta.
  • Ya cancelada (status: "cancelled") — permitido solo mientras aún esté dentro del período en curso (currentPeriodEnd en el futuro). Vuelve a active y el próximo cobro se programa para currentPeriodEnd, así que no se cobra nada al reactivar.
http
PATCH /v1/subscriptions/:id/reactivate
bash
curl -X PATCH https://api-pay.zelta.dev/v1/subscriptions/7316666777788889999/reactivate \
  -H "X-API-Key: tu-api-key-de-proyecto"

Responde con el mensaje "Subscription reactivated" y emite subscription.resumed. Falla con ERR_INVALID_SUBSCRIPTION_TRANSITION si la suscripción está activa sin cancelación pendiente, y con ERR_SUBSCRIPTION_PERIOD_ELAPSED si está cancelled y el período pagado ya venció — pasado ese punto el período terminó y debe crearse una nueva suscripción.

Dunning (renovaciones fallidas) ​

Cuando un cargo de renovación es rechazado, la suscripción entra en past_due y el cargo se reintenta automáticamente en un calendario fijo — el intento original más cuatro reintentos en los días +1, +3, +5 y +7 desde la fecha de vencimiento original (una ventana de gracia de 7 días). Cada intento fallido emite invoice.payment_failed; mientras queden reintentos, también emite subscription.past_due con el siguiente nextBillingAt, y la factura queda open. Si el reintento del día 7 también falla, la factura pasa a failed y la suscripción se cancela y emite subscription.cancelled con reason: "dunning_exhausted".

Los cobros recurrentes se procesan en revisiones periódicas, así que pueden ejecutarse unos minutos después de nextBillingAt.

Procesador mal configurado

Si una renovación no puede cobrarse porque el procesador que guarda la tarjeta está deshabilitado, eliminado o sus credenciales no se pueden leer, no se inician reintentos de dunning: recibes y el cobro se vuelve a intentar más tarde, hasta que corrijas el procesador.

Para rescatar una suscripción past_due, a uno con una tarjeta que funcione — los reintentos ya programados cobrarán al nuevo vault.

Listar las facturas de una suscripción ​

Devuelve las facturas (una por período facturado) generadas para una suscripción.

http
GET /v1/subscriptions/:id/invoices

Parámetros de consulta: paginación estándar (lim de 1 a 100, off) más un filtro opcional status (open, paid, failed, void).

bash
curl "https://api-pay.zelta.dev/v1/subscriptions/7316666777788889999/invoices?status=paid" \
  -H "X-API-Key: tu-api-key-de-proyecto"

Cada factura lleva su amount, currency, status, periodStart/periodEnd y un itemsSnapshot congelado al momento de facturar (un JSON en texto con los items cobrados, o null en facturas antiguas), de modo que la composición cobrada se preserva aunque el catálogo cambie después.

Siguientes pasos ​

  • — cobra addons y productos únicos, con proración
  • — gestiona los clientes vaulteados a los que facturas
  • — subscription.* e invoice.*

Documentación oficial de Zelta