Ir al contenido

Suscripciones

Ver .md

Antes de integrar las suscripciones ten en cuenta:

  • Ambiente (test / producción): el token de acceso define el ambiente. Un token de pruebas (api-access:test) solo puede leer y crear datos de pruebas; un token de producción (api-access:production) solo opera sobre datos de producción. Las lecturas quedan filtradas automáticamente por el ambiente del token.
  • Nombres alternativos: además de los nombres históricos (group, plan, subscriber) puedes usar los alias product, price y customer, que apuntan a los mismos recursos. Ejemplo: GET /api/v1/subscriptions/customer equivale a GET /api/v1/subscriptions/subscriber.
  • Descuentos: los descuentos ahora se gestionan con Cupones reutilizables (porcentaje o monto fijo). Los campos discount_* del plan se mantienen por compatibilidad pero se recomienda usar cupones.

Todas las escrituras (POST/PUT/DELETE) de subscriptions/* aceptan el header Idempotency-Key. Si repites una solicitud con la misma key y los mismos parámetros, se reproduce la respuesta original (con el header Idempotency-Replayed: true) sin volver a ejecutar el cobro o la creación. Es la forma segura de reintentar ante errores de red.

Ventana de terminal
curl -X POST "/api/v1/subscriptions/subscription" \
-H 'Authorization: Bearer ACCESS_TOKEN' \
-H 'Content-type: application/json' \
-H 'Idempotency-Key: 9b1f0e34-6b2a-4a7e-9d0a-unico-por-operacion' \
-d '{ "plan_id": "...", "subscriber_id": "...", "card": "...", "office": 1 }'

Al consultar una suscripción puedes incrustar relaciones con expand[] y así evitar llamadas adicionales. Valores soportados: plan, subscriber, next_transaction.

Ventana de terminal
curl -X GET "/api/v1/subscriptions/subscription/your-subscription-id?expand[]=plan&expand[]=subscriber" \
-H 'Authorization: Bearer ACCESS_TOKEN' \
-H 'Content-type: application/json'

El campo status (presente en las respuestas de suscripción) refleja el estado actual de la suscripción:

status Significado
trial En período de prueba.
activa Activa y al día.
en_gracia Un cobro falló pero sigue dentro del período de gracia (past due).
pausada Cobros pausados (ver Pause).
cancelada Cancelada y sin servicio. Ver la nota de abajo.
finalizada Terminada; no se realizarán más cobros.

Esta página tiene una peculiaridad que conviene conocer antes de escribir el parser:

Qué llamas Qué recibes
Las consultas (GET) snake_case: subscription_plan_id, next_transaction, trial_ends_at, is_active…
Las acciones (cancelar, pausar, reanudar, cambiar de plan) camelCase: planId, nextRenewAt, isActive, onGracePeriod…

No son los mismos campos con otro nombre: son dos representaciones distintas de la suscripción. No reutilices el parser de una para la otra. Cada sección de abajo muestra la que le corresponde.

Los campos históricos conservan su formato para no romper integraciones, y junto a ellos hay campos nuevos en ISO 8601 con offset explícito, que no obligan a adivinar la zona horaria:

Campo Formato Ejemplo Dónde
starts_at, ends_at, trial_ends_at, canceled_at, paused_at, resumes_at Y-m-d H:i:s, hora de Colombia 2026-10-28 11:28:00 GET, acciones y webhooks
next_transaction Y-m-d, día en Colombia 2026-10-28 GET con expand[]=next_transaction
next_renew_at Y-m-d, día en Colombia 2026-10-28 next-transaction, transaction-history y next_charge del webhook
ends_at_iso ISO 8601 con offset 2026-10-28T11:28:00-05:00 GET y webhooks
next_charge_at ISO 8601 con offset 2026-10-28T19:00:00-05:00 GET con expand[]=next_transaction
charge_scheduled_at ISO 8601 con offset 2026-10-28T19:00:00-05:00 next-transaction, transaction-history y next_charge del webhook
cancel_at ISO 8601 con offset 2026-10-28T11:28:00-05:00 GET, cancelación (cancelAt) y webhooks

next_transaction, next-transaction.next_renew_at y next_charge.next_renew_at son el mismo día. Antes next-transaction devolvía la medianoche en UTC (2026-10-28T05:00:00.000000Z); ahora es 2026-10-28, como los demás.

metadata es un objeto de pares clave-valor para guardar tus propias referencias (id de usuario, de pedido, canal…), al estilo de Stripe. Existe en suscripciones, suscriptores, planes y cobros generados. Nosotros no lo usamos: te lo devolvemos.

Límite Valor
Claves Hasta 50
Formato de la clave 1 a 40 caracteres, [A-Za-z0-9_-]
Valor Texto o número, hasta 500 caracteres. Se guarda como texto (42 vuelve como "42")
Al actualizar envías Resultado
{"metadata": {"plan_tier": "gold"}} Se combina con las claves que ya había
{"metadata": {"plan_tier": ""}} o null como valor Se borra esa clave
{"metadata": null} Se borra toda la metadata

La recibes en los GET y listados y en los webhooks de suscripción (subscription.metadata, y también dentro de subscription.plan y subscription.subscriber).

Para buscar por ella, filtra con coincidencia exacta:

Ventana de terminal
curl -X GET \
'https://sag.efipay.co/api/v1/subscriptions/subscription?filter[metadata][user_id]=42' \
-H 'Authorization: Bearer TU_TOKEN' \
-H 'Accept: application/json'

Si no cumple los límites, la respuesta es un 422 con error.code: "validation_failed" y error.param: "metadata".

Descripción: Devuelve la lista paginada de todas tus suscripciones (filtradas por el ambiente del token: pruebas o producción). Útil para tableros, sincronizaciones y reconciliación.

GET /api/v1/subscriptions/subscription
GEThttps://sag.efipay.co/api/v1/subscriptions/subscription

Todos son opcionales y se combinan entre sí. Sin ninguno, la respuesta es la lista completa del ambiente del token.

Parámetro Tipo Qué hace
filter[subscriber_id] UUID exacto Todas las suscripciones de un suscriptor
filter[plan_id] UUID exacto Todas las de un plan
filter[status] exacto trial, activa, en_gracia, pausada, cancelada, finalizada
filter[email] parcial Por el correo del suscriptor
filter[subscription_id] parcial Por el id de la suscripción
filter[description] parcial Por la descripción que guardaste al crearla
filter[plan_name] parcial Por el nombre del plan
filter[metadata][clave] exacto Por un par de tu metadata, por ejemplo filter[metadata][user_id]=42
filter[active] 1, 0, all Si está dando servicio ahora mismo
filter[start_date] fecha Creadas desde esa fecha (inclusive)
filter[finish_date] fecha Creadas hasta esa fecha (inclusive)
sort campo created_at, starts_at, ends_at, trial_ends_at. Prefija con - para descendente
Ventana de terminal
curl -X GET \
'https://sag.efipay.co/api/v1/subscriptions/subscription?filter[email]=ana@correo.com&filter[status]=activa' \
-H 'Authorization: Bearer TU_TOKEN' \
-H 'Accept: application/json'

Descripción: Devuelve el detalle de una suscripción a partir de su id. Acepta objetos expandibles para incluir el plan, el suscriptor o el próximo cobro.

GEThttps://sag.efipay.co/api/v1/subscriptions/subscription/your-subscription-id
200OK
{
"id": "9da5903d-3d1a-4f60-b487-b6b6cd7d9d1b",
"description": null,
"metadata": { "user_id": "42" },
"webhook_url": "https://tu-comercio.com/webhook",
"trial_ends_at": "2026-07-15 00:00:00",
"discount_ends_at": null,
"starts_at": "2026-07-15 00:00:00",
"ends_at": "2026-08-15 00:00:00",
"grace_ends_at": "2026-08-18 00:00:00",
"ends_at_iso": "2026-08-15T00:00:00-05:00",
"next_transaction": "2026-08-15",
"next_charge_at": "2026-08-15T19:00:00-05:00",
"canceled_at": null,
"paused_at": null,
"resumes_at": null,
"environment": "production",
"status": "activa",
"on_trial": false,
"on_discount": false,
"on_grace_period": false,
"paused": false,
"canceled": false,
"cancel_at_period_end": false,
"cancel_at": null,
"is_active": true,
"is_ended": false,
"subscription_plan_id": "9a4698e5-d936-4298-84dc-4a770bfacc19",
"subscriber_id": "9a469901-d010-4afe-94fe-da6790a4f72b",
"office_id": "9a3bf0a4-9854-418f-aa05-5a04b8ce4372",
"created_at": "2026-07-01 09:12:00",
"updated_at": "2026-07-15T00:00:00.000000Z"
}
Campo Tipo Descripción
id uuid Id de la suscripción
description string | null Texto libre tuyo, hasta 255 caracteres
metadata objeto | null Tus pares clave-valor. Ver Metadata
webhook_url string | null Dónde te avisamos los cambios
status string trial, activa, en_gracia, pausada, cancelada, finalizada. Una cancelación al final del período sigue en activa/en_gracia hasta cancel_at
environment string production o testing
starts_at fecha | null Inicio del período vigente
ends_at fecha | null Fin del período vigente. Hasta aquí hay servicio
ends_at_iso ISO 8601 | null El mismo ends_at, con offset explícito
grace_ends_at fecha | null Fin de la ventana de gracia para reintentar el cobro
trial_ends_at fecha | null Fin de la prueba
discount_ends_at fecha | null Fin del descuento
canceled_at fecha | null Cuándo se canceló. Con at_period_end: false es igual a ends_at
cancel_at_period_end bool Cancelada pero todavía dando servicio hasta cancel_at
cancel_at ISO 8601 | null Cuándo deja de dar servicio una suscripción cancelada: ends_at, o el fin de la gracia si es posterior. null si no está cancelada
paused_at fecha | null Cuándo se pausaron los cobros
resumes_at fecha | null Cuándo se reanudan. null con paused_at = pausa indefinida
next_transaction Y-m-d | null Día del próximo cobro programado, en Colombia. Con expand[]=next_transaction
next_charge_at ISO 8601 | null Instante en que el proceso diario intentará ese cobro (19:00 de Colombia). Con expand[]=next_transaction. Ver Fechas
on_trial bool En período de prueba
on_discount bool Con descuento vigente
on_grace_period bool Dentro de la gracia: el cobro falló pero sigue habiendo servicio
paused bool Cobros pausados ahora mismo
canceled bool Cancelada. Puede ser true con is_active en true (ver cancel_at_period_end)
is_active bool Si hay servicio ahora mismo. Es el que debes mirar para dar o cortar acceso
is_ended bool El período terminó
subscription_plan_id uuid Plan
subscriber_id uuid Suscriptor
office_id — Sede
created_at / updated_at fecha

Descripción: Lista las suscripciones asociadas a un plan específico. Útil para saber cuántos clientes están suscritos a un plan.

GEThttps://sag.efipay.co/api/v1/subscriptions/subscription/plan/your-plan-id

Descripción: Lista las suscripciones de un suscriptor (cliente) específico.

GEThttps://sag.efipay.co/api/v1/subscriptions/subscription/subscriber/your-subscriber-id

Descripción: Crea una suscripción a partir de un plan y un suscriptor ya creados, más los datos de la tarjeta con la que se cobrará la recurrencia. La tarjeta se tokeniza de forma segura; si prefieres, puedes enviar un card (token ya generado en Tokenizado) y usaremos ese token para los cobros. El primer cobro se realiza al momento (o al terminar el período de prueba, si el plan lo tiene).

La tarjeta puede llegar de dos maneras excluyentes: como card (un token ya generado en Tokenizado) o como el objeto card_information con los datos en claro. Envía una u otra, nunca las dos.

Nombre del campo Descripción Reglas
plan_id Id del plan a cobrar. Debe estar activo y pertenecer a la misma sucursal que envías en office ['required', 'exists:subscription_plans,id']
subscriber_id Id del suscriptor. Debe pertenecer a la misma sucursal que envías en office ['required', 'exists:subscribers,id']
office Sucursal de la suscripción. Debe ser una de tus sucursales ['required', 'exists:offices,id']
description Descripción libre de esta suscripción ['nullable', 'string', 'max:255']
metadata Tus pares clave-valor. Ver Metadata ['sometimes', 'nullable', 'metadata']
webhook_url URL a la que notificaremos cada cobro de esta suscripción. Recibe un POST firmado con la cabecera Signature. Ver webhooks ['nullable', 'url']
card Token de una tarjeta guardada. Si lo envías, no envíes card_information ['required_without:card_information', 'missing_with:card_information', 'string']
card_information.holder Nombre impreso en la tarjeta ['sometimes', 'required', 'missing_with:card', 'string', 'max:80']
card_information.number Número de la tarjeta, sin espacios. Se valida como número de tarjeta real ['sometimes', 'required', 'missing_with:card', 'numeric']
card_information.datetime Vencimiento en formato YYYY-MM, con mes entre 01 y 12. No puede estar vencida ['sometimes', 'required', 'missing_with:card', 'date_format:Y-m', 'after_or_equal:<mes actual>']
card_information.cvv Código de seguridad. La cantidad de dígitos se valida según la franquicia que resulte de card_information.number ['sometimes', 'required', 'missing_with:card', 'numeric']

Solicitud con token

POSThttps://sag.efipay.co/api/v1/subscriptions/subscription
Ventana de terminal
curl -X POST\
"/api/v1/subscriptions/subscription"\
-H 'Authorization: Bearer ACCESS_TOKEN' \
-H "Content-type: application/json" \
-d '{
"plan_id": "9a9331d9-ea9a-4ad4-a9a4-0d23bd91fff1",
"subscriber_id": "9a933275-1573-497c-916c-ba7fe4f99326",
"card": "your-token-card-here",
"office": 1
}'

Ejemplo de solicitud sin token:

POSThttps://sag.efipay.co/api/v1/subscriptions/subscription
Ventana de terminal
curl -X POST\
"/api/v1/subscriptions/subscription"\
-H 'Authorization: Bearer ACCESS_TOKEN' \
-H "Content-type: application/json" \
-d '{
"plan_id": "9a9331d9-ea9a-4ad4-a9a4-0d23bd91fff1",
"subscriber_id": "9a933275-1573-497c-916c-ba7fe4f99326",
"card_information": {
"holder": "Holder Holder",
"number": "5249314023340339",
"datetime": "2026-02",
"cvv": 899
},
"office": 1
}'

PUT /api/v1/subscriptions/subscription/{subscription-id}

Descripción: Actualiza los datos propios del integrador: description y metadata. No cambia el plan, la tarjeta ni las fechas; para eso están cambiar de plan y actualizar el método de pago.

Nombre del campo Descripción Reglas
description Descripción libre de la suscripción ['sometimes', 'nullable', 'string', 'max:255']
metadata Se combina con la existente. Una clave con "" o null se borra; metadata: null borra todo ['sometimes', 'nullable', 'metadata']
PUThttps://sag.efipay.co/api/v1/subscriptions/subscription/9da5903d-3d1a-4f60-b487-b6b6cd7d9d1b
Ventana de terminal
curl -X PUT \
'/api/v1/subscriptions/subscription/9da5903d-3d1a-4f60-b487-b6b6cd7d9d1b' \
-H 'Authorization: Bearer ACCESS_TOKEN' \
-H 'Content-type: application/json' \
-d '{ "metadata": { "user_id": "42", "channel": "" } }'
200OK

Devuelve el objeto de la suscripción, igual que obtener una suscripción:

{
"id": "9da5903d-3d1a-4f60-b487-b6b6cd7d9d1b",
"description": "Cliente premium",
"metadata": { "user_id": "42" },
"status": "activa",
"...": "..."
}
422Unprocessable Entity
{
"message": "El campo metadata admite como máximo 50 claves.",
"errors": {
"metadata": ["El campo metadata admite como máximo 50 claves."]
},
"error": {
"type": "invalid_request_error",
"code": "validation_failed",
"message": "El campo metadata admite como máximo 50 claves.",
"param": "metadata"
}
}

Descripción: Cancela una suscripción activa. Por defecto la cancelación es al final del período vigente: el cliente conserva el servicio hasta ends_at.

PUThttps://sag.efipay.co/api/v1/subscriptions/subscription/cancel/your-subscription-id
Cuerpo canceled_at ends_at is_active status Qué significa
(vacío) o {"at_period_end": true} Ahora No cambia true hasta cancel_at activa (o en_gracia) hasta cancel_at, luego cancelada El cliente ya pagó el ciclo: lo termina
{"at_period_end": false} Ahora Ahora false de inmediato cancelada Corte inmediato, también de la gracia

Mientras una cancelación al final del período no llega a su fin, la suscripción trae canceled: true, cancel_at_period_end: true y cancel_at (ISO 8601 con offset, por ejemplo "2026-10-28T11:28:00-05:00"; es ends_at, o el fin de la gracia si es posterior). Es igual en el GET, en la respuesta de esta llamada (cancelAtPeriodEnd, cancelAt) y en el webhook canceled.

Ventana de terminal
curl -X PUT \
'https://sag.efipay.co/api/v1/subscriptions/subscription/cancel/your-subscription-id' \
-H 'Authorization: Bearer TU_TOKEN' \
-H 'Content-type: application/json' \
-d '{ "at_period_end": false }'
200OK
{
"canceled": true,
"at_period_end": true,
"subscription": {
"id": "9da5903d-3d1a-4f60-b487-b6b6cd7d9d1b",
"status": "activa",
"canceled": true,
"cancelAtPeriodEnd": true,
"cancelAt": "2026-10-28T11:28:00-05:00",
"isActive": true
}
}

Con at_period_end: false llega status: "cancelada", cancelAtPeriodEnd: false e isActive: false.

400Bad Request
{
"canceled": false,
"message": "La suscripción ya está cancelada.",
"error": {
"type": "invalid_request_error",
"code": "subscription_already_canceled",
"message": "La suscripción ya está cancelada.",
"param": null
}
}

Descripción: Pausa los cobros de una suscripción. Mientras esté pausada, el motor de recurrencia no realiza cobros. Puedes programar una reanudación automática con resumes_at; si no lo envías, queda pausada hasta que llames a Reanudar cobros.

Nombre del campo Descripción Reglas
resumes_at Fecha y hora en que la suscripción se reanuda sola. Debe ser futura. Si la omites, la pausa dura hasta que llames a reanudar ['sometimes', 'nullable', 'date', 'after:now']
POSThttps://sag.efipay.co/api/v1/subscriptions/subscription/9da5903d-3d1a-4f60-b487-b6b6cd7d9d1b/pause
Ventana de terminal
curl -X POST\
"/api/v1/subscriptions/subscription/9da5903d-3d1a-4f60-b487-b6b6cd7d9d1b/pause"\
-H 'Authorization: Bearer ACCESS_TOKEN' \
-H "Content-type: application/json" \
-d '{ "resumes_at": "2026-09-01 00:00:00" }'
200OK
{
"paused": true,
"subscription": {
"id": "9da5903d-3d1a-4f60-b487-b6b6cd7d9d1b",
"status": "pausada",
"paused": true,
"pausedAt": "2026-07-30 10:00:00",
"resumesAt": "2026-09-01 00:00:00"
}
}
400Bad Request
{
"paused": false,
"message": "No se puede pausar una suscripción cancelada."
}

Descripción: Reanuda los cobros de una suscripción pausada.

POSThttps://sag.efipay.co/api/v1/subscriptions/subscription/9da5903d-3d1a-4f60-b487-b6b6cd7d9d1b/resume
Ventana de terminal
curl -X POST\
"/api/v1/subscriptions/subscription/9da5903d-3d1a-4f60-b487-b6b6cd7d9d1b/resume"\
-H 'Authorization: Bearer ACCESS_TOKEN' \
-H "Content-type: application/json"
200OK
{
"resumed": true,
"subscription": {
"id": "9da5903d-3d1a-4f60-b487-b6b6cd7d9d1b",
"status": "activa",
"paused": false
}
}

Descripción: Actualiza la tarjeta con la que se cobran las recurrencias de una suscripción. La tarjeta se tokeniza de forma segura y reemplaza a la anterior.

Nombre del campo Descripción Reglas
number Número de la nueva tarjeta, sin espacios. Se valida como número de tarjeta real ['required', 'numeric']
datetime Vencimiento en formato YYYY-MM (por ejemplo 2030-05). No puede estar vencida ['required', 'date']
cvv Código de seguridad. La cantidad de dígitos se valida según la franquicia que resulte de number ['required', 'numeric']
PUThttps://sag.efipay.co/api/v1/subscriptions/subscription/9da5903d-3d1a-4f60-b487-b6b6cd7d9d1b/update-payment-method
Ventana de terminal
curl -X PUT\
"/api/v1/subscriptions/subscription/9da5903d-3d1a-4f60-b487-b6b6cd7d9d1b/update-payment-method"\
-H 'Authorization: Bearer ACCESS_TOKEN' \
-H "Content-type: application/json" \
-d '{
"number": "5249314023340339",
"datetime": "2028-05",
"cvv": 899
}'
200OK
{ "updated": true }
400Bad Request
{ "message": "No se puede actualizar el método de pago de una suscripción cancelada" }

POST /api/v1/subscriptions/subscription/{subscription-id}/renew

Descripción: Le envía al suscriptor una invitación de renovación por correo, con un enlace en el que confirma que quiere seguir. Sirve para reactivar una suscripción que ya terminó.

No recibe cuerpo. El suscriptor debe tener una tarjeta guardada, porque la invitación la reutiliza.

POSThttps://sag.efipay.co/api/v1/subscriptions/subscription/9da5903d-3d1a-4f60-b487-b6b6cd7d9d1b/renew
Ventana de terminal
curl -X POST \
'/api/v1/subscriptions/subscription/9da5903d-3d1a-4f60-b487-b6b6cd7d9d1b/renew' \
-H 'Authorization: Bearer ACCESS_TOKEN' \
-H 'Accept: application/json'
200OK
{
"renewed": true,
"message": "Se ha enviado una invitación de renovación al suscriptor por correo electrónico."
}
400Bad Request
{
"renewed": false,
"message": "Ya existe una invitación de renovación pendiente para esta suscripción."
}
400Bad Request
{
"renewed": false,
"message": "El suscriptor no tiene una tarjeta guardada. No se puede enviar la invitación."
}

Descripción: Cambia el plan de una suscripción de forma inmediata. No requiere que el suscriptor acepte nada.

El prorrateo calcula:

  • Un crédito por el tiempo no usado del plan actual.
  • Un cargo por el tiempo restante del período con el nuevo plan.
  • El neto (net) = cargo − crédito. Qué se hace con él lo decide proration_behavior, con la misma semántica que en Stripe.

El cobro pendiente del próximo ciclo se re-tarifica automáticamente al precio del nuevo plan.

proration_behavior Neto positivo (upgrade) Neto negativo (downgrade) applies_on
create_prorations (por defecto) Se suma a la próxima renovación. Hoy no se cobra nada Baja la próxima renovación. Si el crédito supera toda la renovación, va al balance del suscriptor "next_renewal"
always_invoice Se cobra de inmediato con la tarjeta guardada Va al balance del suscriptor "now"
none No se cobra ni se acredita nada No se cobra ni se acredita nada null
  • create_prorations: el ajuste aparece como una línea aparte con proration: true en la próxima factura, y el cobro pendiente expone proration_amount en next-transaction y en el historial.
  • always_invoice: el cobro aparece en el historial y la respuesta trae proration.transaction, el mismo objeto de transacción del checkout. Si ese cobro se rechaza, el plan no cambia y la respuesta es un 402 (ver abajo).
  • none: el plan cambia ya y la próxima renovación cobra el precio del plan nuevo. La respuesta trae net: 0, line_items: [] y el cálculo teórico en proration.preview, solo como referencia.
Nombre del campo Descripción Reglas
plan_id Id del nuevo plan. Debe estar activo, ser de tu comercio y del mismo ambiente que la suscripción ['required', 'exists:subscription_plans,id']
mode direct (por defecto) aplica el cambio de inmediato. invitation le envía al suscriptor una invitación por correo para que lo apruebe. Sin mode, una suscripción inactiva va por invitación; con mode: "direct" explícito sobre una cancelada o inactiva responde 400 ['sometimes', 'in:direct,invitation']
proration_behavior Qué hacer con el neto: create_prorations (por defecto) lo suma a la próxima renovación, always_invoice lo cobra de inmediato, none no prorratea. Ver la tabla ['sometimes', 'in:create_prorations,none,always_invoice']
PUThttps://sag.efipay.co/api/v1/subscriptions/subscription/9da5903d-3d1a-4f60-b487-b6b6cd7d9d1b/change-plan
Ventana de terminal
curl -X PUT\
"/api/v1/subscriptions/subscription/9da5903d-3d1a-4f60-b487-b6b6cd7d9d1b/change-plan"\
-H 'Authorization: Bearer ACCESS_TOKEN' \
-H "Content-type: application/json" \
-d '{
"plan_id": "9a4698e5-d936-4298-84dc-4a770bfacc19"
}'
200OK
{
"changed": true,
"proration": {
"currency": "COP",
"unused_fraction": 0.6,
"credit_unused": 6000,
"charge_new": 18000,
"net": 12000,
"line_items": [
{ "description": "Tiempo no usado de Plan Básico", "amount": -6000 },
{ "description": "Tiempo restante de Plan Premium", "amount": 18000 }
],
"behavior": "create_prorations",
"applies_on": "next_renewal"
},
"subscription": {
"id": "9da5903d-3d1a-4f60-b487-b6b6cd7d9d1b",
"planId": "9a4698e5-d936-4298-84dc-4a770bfacc19",
"planName": "Plan Premium",
"status": "activa"
}
}
200OK
{
"changed": true,
"proration": {
"currency": "COP",
"net": 12000,
"line_items": [ "..." ],
"behavior": "always_invoice",
"applies_on": "now",
"transaction": {
"transaction_id": 20490,
"status": "Aprobada",
"status_key": "approved",
"response_code": "00",
"error": null,
"amount": 12000
}
},
"subscription": { "...": "..." }
}
200OK
{
"changed": true,
"proration": {
"currency": "COP",
"unused_fraction": 0.6,
"credit_unused": 0,
"charge_new": 0,
"net": 0,
"line_items": [],
"behavior": "none",
"applies_on": null,
"preview": {
"credit_unused": 6000,
"charge_new": 18000,
"net": 12000,
"line_items": [ "..." ]
}
},
"subscription": { "...": "..." }
}
402
{
"changed": false,
"message": "El cobro del prorrateo no fue aprobado; el plan no cambió.",
"error": {
"type": "card_error",
"code": "card_declined",
"decline_code": "51",
"message": "El cobro del prorrateo no fue aprobado; el plan no cambió.",
"param": null
},
"proration": {
"net": 12000,
"behavior": "always_invoice",
"applies_on": "now",
"transaction": {
"transaction_id": 20491,
"status": "Rechazada",
"status_key": "rejected",
"response_code": "51",
"error": { "code": "51", "message": "…", "retryable": false, "action": "contact_issuer" }
}
}
}

error.decline_code es el response_code de la transacción rechazada. Ver códigos de red.

400Bad Request
{
"changed": false,
"message": "No se puede cambiar el plan de una suscripción cancelada.",
"error": {
"type": "invalid_request_error",
"code": "subscription_canceled",
"message": "No se puede cambiar el plan de una suscripción cancelada.",
"param": null
}
}

Con mode: "direct" explícito, una suscripción cancelada responde subscription_canceled y una inactiva subscription_inactive, en lugar de caer sin aviso en el flujo por invitación. Si no envías mode, una suscripción inactiva sigue yendo por invitación.

Si envías mode: "invitation", en lugar de aplicar el cambio se crea una invitación change_plan que se envía por correo al suscriptor. El cambio se ejecuta solo cuando el suscriptor la acepta. Rutas públicas del flujo:

  • GET /subscription-invitation/{token}: ver detalle de la invitación.
  • POST /subscription-invitation/{token}/accept: aceptar.
  • POST /subscription-invitation/{token}/decline: rechazar.
Vigencia 7 días desde el envío
Simultáneas Una sola invitación change_plan pendiente por suscripción. Una segunda llamada responde 400
Requisito El suscriptor debe tener una tarjeta guardada; si no, 400 sin enviar el correo
Al aceptar Se aplica el cambio y llega el webhook plan_changed, con transaction si hubo cobro
Al rechazar La invitación queda en declined. No se emite webhook
Al expirar La invitación deja de ser válida. Hoy no se emite ningún webhook al expirar

En ambos modos, si la suscripción tiene webhook_url, se notifica el evento plan_changed. El cuerpo incluye previous_plan_id con el plan que tenía antes.

Descripción: Previsualiza el prorrateo de un cambio de plan sin aplicarlo (la factura que se generaría con el cambio). Ideal para mostrarle al cliente cuánto se le cobrará o acreditará antes de confirmar.

Nombre del campo Descripción Reglas
plan_id Id del plan destino, como parámetro de consulta. Debe ser de tu comercio y del mismo ambiente que la suscripción. Aquí no se exige que esté activo ['required', 'exists:subscription_plans,id']
GEThttps://sag.efipay.co/api/v1/subscriptions/subscription/9da5903d-3d1a-4f60-b487-b6b6cd7d9d1b/change-plan-preview?plan_id=9a4698e5-d936-4298-84dc-4a770bfacc19
Ventana de terminal
curl -X GET\
"/api/v1/subscriptions/subscription/9da5903d-3d1a-4f60-b487-b6b6cd7d9d1b/change-plan-preview?plan_id=9a4698e5-d936-4298-84dc-4a770bfacc19"\
-H 'Authorization: Bearer ACCESS_TOKEN' \
-H "Content-type: application/json"
200OK
{
"proration": {
"currency": "COP",
"unused_fraction": 0.6,
"credit_unused": 6000,
"charge_new": 18000,
"net": 12000,
"line_items": [
{ "description": "Tiempo no usado de Plan Básico", "amount": -6000 },
{ "description": "Tiempo restante de Plan Premium", "amount": 18000 }
]
}
}

Descripción: Aplica un cupón (por su código) a una suscripción. El descuento se refleja en los cobros recurrentes según la duración del cupón (once, repeating o forever).

Nombre del campo Descripción Reglas
code Código del cupón a aplicar. Debe existir en tu comercio, estar activo y todavía ser redimible ['required', 'string']
POSThttps://sag.efipay.co/api/v1/subscriptions/subscription/9da5903d-3d1a-4f60-b487-b6b6cd7d9d1b/apply-coupon
Ventana de terminal
curl -X POST\
"/api/v1/subscriptions/subscription/9da5903d-3d1a-4f60-b487-b6b6cd7d9d1b/apply-coupon"\
-H 'Authorization: Bearer ACCESS_TOKEN' \
-H "Content-type: application/json" \
-d '{ "code": "WELCOME10" }'
200OK
{
"applied": true,
"coupon_id": "9b0f2a13-2c44-4a1a-9f2e-1d3b4c5d6e7f",
"discount_ends_at": "2026-12-30 00:00:00"
}
400Bad Request
{
"applied": false,
"message": "Cupón inválido o no disponible."
}

Descripción: Lista las facturas de una suscripción. Cada factura representa un ciclo de facturación con su estado (open, paid, void, uncollectible), subtotal, impuesto, total y período.

GEThttps://sag.efipay.co/api/v1/subscriptions/subscription/9da5903d-3d1a-4f60-b487-b6b6cd7d9d1b/invoices
200OK
{
"current_page": 1,
"data": [
{
"id": "9b0f4d21-8a3e-4c9b-9a1e-2f3a4b5c6d7e",
"number": "INV-2026-000012",
"status": "paid",
"subtotal": 12605.04,
"discount_total": 0,
"tax_total": 2394.96,
"total": 15000,
"currency_type": "COP",
"period_start": "2026-07-01 00:00:00",
"period_end": "2026-08-01 00:00:00",
"paid_at": "2026-07-01 19:00:05",
"subscription_id": "9da5903d-3d1a-4f60-b487-b6b6cd7d9d1b"
}
],
"total": 1,
"last_page": 1
}

Descripción: Vista previa (borrador) de la próxima factura de la suscripción, con el descuento vigente ya aplicado y el prorrateo que ya lleva sumado. No genera ningún cobro.

Con plan_id anticipa un cambio de plan con los mismos parámetros que enviarías a change-plan, como el upcoming invoice de Stripe:

Parámetro de consulta Descripción Reglas
plan_id Plan destino a simular. De tu comercio y del mismo ambiente ['sometimes', 'exists:subscription_plans,id']
proration_behavior Comportamiento a simular. Por defecto create_prorations ['sometimes', 'in:create_prorations,none,always_invoice']
GEThttps://sag.efipay.co/api/v1/subscriptions/subscription/9da5903d-3d1a-4f60-b487-b6b6cd7d9d1b/upcoming-invoice
200OK
{
"upcoming_invoice": {
"status": "draft",
"currency_type": "COP",
"subtotal": 12605.04,
"tax_total": 2394.96,
"total": 15000,
"period_start": "2026-08-01 00:00:00",
"next_charge_at": "2026-08-01",
"line_items": [
{ "description": "Suscripción Plan Básico", "amount": 15000, "quantity": 1, "proration": false }
]
}
}
Campo Descripción
total Lo que se cobrará en la próxima renovación, incluido el prorrateo ya sumado con create_prorations
next_charge_at Día de ese cobro, Y-m-d en Colombia. Se cobra ese día a las 19:00 (ver Fechas)
line_items[].proration true en la línea «Prorrateo por cambio de plan», que va aparte de la del plan
immediate_charge Solo al simular always_invoice con neto positivo: {total, charge_at, line_items}, lo que se cobraría ahora mismo

Simulando un cambio con create_prorations:

Ventana de terminal
curl -X GET \
'/api/v1/subscriptions/subscription/9da5903d-3d1a-4f60-b487-b6b6cd7d9d1b/upcoming-invoice?plan_id=9a4698e5-d936-4298-84dc-4a770bfacc19&proration_behavior=create_prorations' \
-H 'Authorization: Bearer ACCESS_TOKEN'
{
"upcoming_invoice": {
"status": "draft",
"currency_type": "COP",
"total": 42000,
"next_charge_at": "2026-08-01",
"line_items": [
{ "description": "Suscripción Plan Premium", "amount": 30000, "quantity": 1, "proration": false },
{ "description": "Prorrateo por cambio de plan", "amount": 12000, "quantity": 1, "proration": true }
]
}
}

Simulando con always_invoice: el ajuste no va a la renovación sino a immediate_charge:

{
"upcoming_invoice": {
"total": 30000,
"next_charge_at": "2026-08-01",
"line_items": [
{ "description": "Suscripción Plan Premium", "amount": 30000, "quantity": 1, "proration": false }
],
"immediate_charge": {
"total": 12000,
"charge_at": "2026-07-19T10:15:00-05:00",
"line_items": [
{ "description": "Tiempo no usado de Plan Básico", "amount": -6000 },
{ "description": "Tiempo restante de Plan Premium", "amount": 18000 }
]
}
}
}

Descripción: Descarga el PDF de una factura específica de la suscripción. La respuesta es el archivo PDF (application/pdf).

GEThttps://sag.efipay.co/api/v1/subscriptions/subscription/9da5903d-3d1a-4f60-b487-b6b6cd7d9d1b/invoices/your-invoice-id/pdf
Ventana de terminal
curl -X GET\
"/api/v1/subscriptions/subscription/9da5903d-3d1a-4f60-b487-b6b6cd7d9d1b/invoices/your-invoice-id/pdf"\
-H 'Authorization: Bearer ACCESS_TOKEN' \
--output factura.pdf

Descripción: Devuelve el historial paginado de cobros (transacciones) realizados por la suscripción, con el detalle de cada pago y su estado.

GEThttps://sag.efipay.co/api/v1/subscriptions/subscription/%7Bsubscription-id%7D/transaction-history
200OK
{
"data": [
{
"id": "01990bf8-e20c-72e4-b408-991e81f70beb",
"amount": 15000,
"currency_type": "COP",
"tax": 0,
"next_renew_at": "2025-09-02",
"charge_scheduled_at": "2025-09-02T19:00:00-05:00",
"proration_amount": null,
"charge_at": "2025-09-02 14:48:06",
"approved": 1,
"subscription_id": "01990bf8-e1f3-7067-9c51-7bb4d1ad05bf",
"transaction": {
"transaction_id": 548,
"amount": 15000,
"currency_iso": "COP",
"amount_cop": 15000,
"tax": 0,
"reference_1": "Subscription: 01990bf8-e1f3-7067-9c51-7bb4d1ad05bf",
"reference_2": "Email: sdfasd@fsdfsd.ds",
"reference_3": "1756842484281",
"status": "Aprobada",
"payment_method": "credit",
"payment_method_source": "Visa",
"network": "credibanco",
"trazability_id": "548",
"authorization_code": "548",
"approved_at": "2025-09-02 14:48:04",
"expired_at": "2025-09-03 14:48:04",
"office_id": 1,
"environment": "production",
"aggregator": true,
"economic_group_id": 1,
"created_at": "2025-09-02 14:48:04",
"customer_payer_id": "01976094-220e-719d-aa2a-7bca1b757f59",
"customer_payer": {
"email": "sdfasd@fsdfsd.ds",
"name": "Osmi Otalora",
"identification_type": "CC",
"id_number": "101006464",
"dialling_code": "57",
"cellphone": "3006776454",
"country": "COL",
"state": "Bogota",
"city": "Bogota",
"address_1": "Bogota",
"address_2": "Bogota",
"zip_code": "0000"
},
"subscription_id": "01990bf8-e1f3-7067-9c51-7bb4d1ad05bf"
}
}
],
"links": {
"first": "http://localhost:8009/api/v1/subscriptions/subscription/01990bf8-e1f3-7067-9c51-7bb4d1ad05bf/transaction-history?page=1",
"last": "http://localhost:8009/api/v1/subscriptions/subscription/01990bf8-e1f3-7067-9c51-7bb4d1ad05bf/transaction-history?page=1",
"prev": null,
"next": null
},
"meta": {
"current_page": 1,
"from": 1,
"last_page": 1,
"links": [
{
"url": null,
"label": "&laquo; Anterior",
"active": false
},
{
"url": "http://localhost:8009/api/v1/subscriptions/subscription/01990bf8-e1f3-7067-9c51-7bb4d1ad05bf/transaction-history?page=1",
"label": "1",
"active": true
},
{
"url": null,
"label": "Siguiente &raquo;",
"active": false
}
],
"path": "http://localhost:8009/api/v1/subscriptions/subscription/01990bf8-e1f3-7067-9c51-7bb4d1ad05bf/transaction-history",
"per_page": 15,
"to": 1,
"total": 1
}
}

Descripción: Devuelve la información del próximo cobro recurrente pendiente: cuánto y cuándo se cobrará. Si aún no hay un cobro programado, transaction será null.

GEThttps://sag.efipay.co/api/v1/subscriptions/subscription/%7Bsubscription-id%7D/next-transaction
200OK
{
"id": "01990bf8-eadc-7064-9996-06f08a7fbcd5",
"amount": 15000,
"currency_type": "COP",
"tax": 0,
"next_renew_at": "2025-10-02",
"charge_scheduled_at": "2025-10-02T19:00:00-05:00",
"proration_amount": null,
"charge_at": null,
"approved": null,
"subscription_id": "01990bf8-e1f3-7067-9c51-7bb4d1ad05bf",
"transaction": null
}
Campo Descripción
next_renew_at Día del cobro, Y-m-d en Colombia. Es el mismo valor que next_transaction de la suscripción. Antes llegaba como medianoche UTC (2025-10-02T05:00:00.000000Z)
charge_scheduled_at Instante en que el proceso diario intentará el cobro: ese día a las 19:00 de Colombia, en ISO 8601 con offset
proration_amount Parte del amount que viene de un cambio de plan con create_prorations. Negativa si fue un downgrade. null o 0 si no hay
amount Total a cobrar, ya con el prorrateo sumado

Última actualización: