Offers
Crea y gestiona offers — las configuraciones adquiribles y con precio de un producto, con ciclos de facturación, periodos de prueba, cargos de configuración y precios por moneda.
Un offer es una configuración adquirible de un producto. El producto modela qué vendes; el offer modela cómo se adquiere y se factura: el ciclo de facturación, el periodo de prueba, el límite de ciclos y uno o más precios — uno por moneda.
Un único producto puede tener muchos offers. Un plan mensual y un plan anual del mismo producto son dos offers. Una promoción y el precio estándar son dos offers. Esta separación te permite lanzar nuevos precios, ejecutar experimentos y localizar importes sin tocar el producto en sí.
Tokeflow es una plataforma de orquestación de pagos, no un procesador de pagos. Los offers describen tus precios; la autorización, la captura y la liquidación reales de cualquier cargo ocurren en el proveedor de pagos conectado. Los objetos del catálogo nunca mueven dinero.
Cómo se facturan los offers
El billing_cycle de un offer define su cadencia:
| Ciclo | Significado |
|---|---|
daily | Se renueva cada día |
biweekly | Se renueva cada dos semanas |
monthly | Se renueva cada mes |
quarterly | Se renueva cada tres meses |
half_yearly | Se renueva cada seis meses |
yearly | Se renueva cada año |
custom | Se renueva cada custom_billing_days días (obligatorio cuando se usa custom) |
none | Bajo demanda, sin planificador — un cargo único sin renovación recurrente |
El cycle_limit limita el número de cargos. Omítelo (o establécelo en null) para cobrar hasta que se cancele la suscripción. Cuando se alcanza el límite, la suscripción expira — a menos que renew_after_cycle_limit sea true, en cuyo caso se crea una nueva suscripción. Si renewal_offer_id está definido, se usa ese offer para la renovación posterior al límite; de lo contrario, se renueva con el mismo offer.
Importe del primer cargo
Cada precio lleva un amount base y un first_charge_amount opcional. El first_charge_amount se aplica cuando el setup_charge del offer es true, y establece el precio del primer ciclo de facturación — útil para un precio de introducción, una tarifa única de configuración o un primer mes con descuento. Un first_charge_amount de 0 significa un cargo de validación de tarjeta (verificar el instrumento sin cobrar).
Un periodo de prueba gratuito anula todo esto: consulta la sección Periodo de prueba gratuito más abajo.
Periodo de prueba gratuito
Cuando free_trial es true, trial_days es obligatorio y define la duración del periodo de prueba en días. Durante el periodo de prueba no se realiza ningún cargo recurrente; la facturación comienza cuando termina el periodo de prueba.
Un periodo de prueba cobra 0 al inicio, y setup_charge no es obligatorio. La regla de precio lee free_trial directamente, así que el primer cobro es 0 tenga o no el offer un cargo de configuración. La tarjeta se sigue registrando — el checkout de importe cero ejecuta una validación de tarjeta — para que el primer cobro real pueda realizarse al terminar el periodo de prueba.
Lo único que setup_charge todavía añade a un periodo de prueba es un precio de adquisición: con setup_charge: true y un first_charge_amount mayor que 0, ese importe se cobra al inicio como tarifa de alta, y el periodo de prueba cubre el primer ciclo con normalidad. Fijarlo igual o por encima de amount no es un periodo de prueba — el cliente ya pagó el primer ciclo, así que la suscripción empieza activa en lugar de en prueba.
amount y first_charge_amount son enteros en unidades menores (centavos). R$99.00 = 9900; US$150.00 = 15000. Empareja siempre un importe con su currency ISO 4217.
Un producto puede tener como máximo un offer predeterminado (is_default = true), y un offer puede tener como máximo un precio predeterminado (is_default = true). Dentro de un offer, cada moneda puede aparecer como máximo una vez.
El objeto offer
{
"id": "ofr_a1b2c3d4e5",
"product_id": "prd_a1b2c3d4e5",
"name": "Mensal",
"slug": "mensal",
"description": "Plano mensal padrão",
"billing_cycle": "monthly",
"custom_billing_days": null,
"cycle_limit": 12,
"free_trial": false,
"trial_days": null,
"setup_charge": true,
"renew_after_cycle_limit": false,
"renewal_offer_id": null,
"is_default": true,
"status": "active",
"created_at": "2026-01-15T12:30:00.000Z",
"updated_at": "2026-01-15T12:30:00.000Z",
"prices": [
{
"id": "opr_b1c2d3e4f5",
"offer_id": "ofr_a1b2c3d4e5",
"currency": "BRL",
"amount": 9900,
"first_charge_amount": 0,
"is_default": true,
"created_at": "2026-01-15T12:30:00.000Z",
"updated_at": "2026-01-15T12:30:00.000Z"
},
{
"id": "opr_c2d3e4f5g6",
"offer_id": "ofr_a1b2c3d4e5",
"currency": "USD",
"amount": 1900,
"first_charge_amount": null,
"is_default": false,
"created_at": "2026-01-15T12:30:00.000Z",
"updated_at": "2026-01-15T12:30:00.000Z"
}
]
}Atributos
| Field | Type | Description |
|---|---|---|
id | string | Identificador único, con prefijo ofr_. |
product_id | string | El producto al que este offer pone precio (prd_). |
name | string | Nombre legible, p. ej. "Mensal". |
slug | string | Identificador en minúsculas, único por producto. |
description | string | null | Descripción opcional. |
billing_cycle | enum | daily biweekly monthly quarterly half_yearly yearly custom none. |
custom_billing_days | integer | null | Periodo en días. Obligatorio cuando billing_cycle = custom. |
cycle_limit | integer | null | Número fijo de cargos. null = cobrar hasta la cancelación. |
free_trial | boolean | Si el offer incluye un periodo de prueba gratuito. |
trial_days | integer | null | Duración del periodo de prueba en días. Obligatorio cuando free_trial = true. |
setup_charge | boolean | Cuando es true, el first_charge_amount del precio se usa para el primer ciclo. |
renew_after_cycle_limit | boolean | Cuando es true y se alcanza el cycle_limit, se crea una nueva suscripción en lugar de expirar. |
renewal_offer_id | string | null | Offer usado para la renovación posterior al límite (ofr_). null = renovar con el mismo offer. Debe pertenecer a la misma familia de productos. |
is_default | boolean | Si este es el offer predeterminado del producto. |
status | enum | active o archived. |
created_at | string | Marca de tiempo ISO 8601 UTC. |
updated_at | string | Marca de tiempo ISO 8601 UTC. |
prices | array | Precios del offer anidados (incluidos en la obtención por ID). |
El objeto precio del offer
| Field | Type | Description |
|---|---|---|
id | string | Identificador único, con prefijo opr_. |
offer_id | string | El offer propietario (ofr_). |
currency | string | Código de moneda ISO 4217, p. ej. BRL. |
amount | integer | Precio recurrente en unidades menores (centavos). |
first_charge_amount | integer | null | Precio del primer ciclo en unidades menores. Se aplica cuando setup_charge = true. 0 = validación de tarjeta. Un offer con free_trial cobra 0 en el primer ciclo de todos modos. |
is_default | boolean | Si este es el precio predeterminado del offer. |
created_at | string | Marca de tiempo ISO 8601 UTC. |
updated_at | string | Marca de tiempo ISO 8601 UTC. |
Endpoints
Todos los endpoints de offers están bajo https://api.tokeflow.com/api/v1. Consulta Autenticación para claves y scopes. Los offers son un recurso con alcance de merchant: con una clave de Organization debes identificar el merchant de destino mediante merchant_id; con una clave de Merchant el merchant se infiere.
POST/api/v1/offers
Crea un offer junto con al menos un precio.
Auth: clave de Organization (con merchant_id) o clave de Merchant. Scope: offers:write.
| Field | Type | Required | Description |
|---|---|---|---|
product_id | string | Sí | Producto al que adjuntar el offer (prd_). |
name | string | Sí | Nombre legible. |
slug | string | Sí | Identificador en minúsculas, único por producto. |
billing_cycle | enum | Sí | Uno de los valores de ciclo de facturación. |
status | enum | Sí | active o archived. |
prices | array | Sí | Al menos un objeto de precio (ver abajo). |
description | string | No | Descripción opcional. |
custom_billing_days | integer | Condicional | Obligatorio cuando billing_cycle = custom. |
cycle_limit | integer | No | Número fijo de cargos. Omitir = cobrar hasta la cancelación. |
free_trial | boolean | No | Por defecto false. |
trial_days | integer | Condicional | Obligatorio cuando free_trial = true. |
setup_charge | boolean | No | Por defecto false. |
renew_after_cycle_limit | boolean | No | Por defecto false. |
renewal_offer_id | string | No | Offer para la renovación posterior al límite (ofr_). |
is_default | boolean | No | Por defecto false. |
Cada entrada en prices:
| Field | Type | Required | Description |
|---|---|---|---|
currency | string | Sí | Código ISO 4217. |
amount | integer | Sí | Precio recurrente en unidades menores (≥ 0). |
first_charge_amount | integer | No | Precio del primer ciclo en unidades menores (≥ 0). |
is_default | boolean | No | Marca el precio predeterminado del offer. |
curl -X POST https://api.tokeflow.com/api/v1/offers \
-H "Authorization: Bearer sk_live_mer_xxx" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: 8f3c1d2a-7b6e-4c0a-9f12-3d4e5f6a7b8c" \
-d '{
"product_id": "prd_a1b2c3d4e5",
"name": "Mensal",
"slug": "mensal",
"billing_cycle": "monthly",
"status": "active",
"setup_charge": true,
"is_default": true,
"prices": [
{ "currency": "BRL", "amount": 9900, "first_charge_amount": 0, "is_default": true },
{ "currency": "USD", "amount": 1900, "is_default": false }
]
}'Respuesta 201 Created:
{
"success": true,
"data": {
"id": "ofr_a1b2c3d4e5",
"product_id": "prd_a1b2c3d4e5",
"name": "Mensal",
"slug": "mensal",
"description": null,
"billing_cycle": "monthly",
"custom_billing_days": null,
"cycle_limit": null,
"free_trial": false,
"trial_days": null,
"setup_charge": true,
"renew_after_cycle_limit": false,
"renewal_offer_id": null,
"is_default": true,
"status": "active",
"created_at": "2026-01-15T12:30:00.000Z",
"updated_at": "2026-01-15T12:30:00.000Z",
"prices": [
{
"id": "opr_b1c2d3e4f5",
"offer_id": "ofr_a1b2c3d4e5",
"currency": "BRL",
"amount": 9900,
"first_charge_amount": 0,
"is_default": true,
"created_at": "2026-01-15T12:30:00.000Z",
"updated_at": "2026-01-15T12:30:00.000Z"
},
{
"id": "opr_c2d3e4f5g6",
"offer_id": "ofr_a1b2c3d4e5",
"currency": "USD",
"amount": 1900,
"first_charge_amount": null,
"is_default": false,
"created_at": "2026-01-15T12:30:00.000Z",
"updated_at": "2026-01-15T12:30:00.000Z"
}
]
},
"request_id": "req_8f3c2a1b",
"timestamp": "2026-01-15T12:30:00.000Z"
}Pasa un encabezado Idempotency-Key (o idempotency_key en el cuerpo) en las llamadas de creación. Reintentar con la misma clave devuelve el resultado original (200 OK) en lugar de crear un duplicado.
GET/api/v1/offers
Lista los offers con paginación y filtros.
Auth: clave de Organization (con merchant_id) o clave de Merchant. Scope: offers:read.
| Query param | Type | Description |
|---|---|---|
page | integer | Número de página, por defecto 1, mínimo 1. |
limit | integer | Elementos por página, por defecto 20, máximo 100. |
product_id | string | Filtrar a un único producto. |
status | enum | active o archived. |
billing_cycle | enum | Uno de los valores de ciclo de facturación. |
is_default | boolean | Filtrar solo a los offers predeterminados. |
name | string | Filtro de coincidencia parcial sobre el nombre del offer. |
merchant_id | string | Obligatorio al usar una clave de Organization. |
curl "https://api.tokeflow.com/api/v1/offers?product_id=prd_a1b2c3d4e5&status=active&limit=20" \
-H "Authorization: Bearer sk_live_mer_xxx"Respuesta 200 OK:
{
"success": true,
"data": [
{
"id": "ofr_a1b2c3d4e5",
"product_id": "prd_a1b2c3d4e5",
"name": "Mensal",
"slug": "mensal",
"description": null,
"billing_cycle": "monthly",
"custom_billing_days": null,
"cycle_limit": null,
"free_trial": false,
"trial_days": null,
"setup_charge": true,
"renew_after_cycle_limit": false,
"renewal_offer_id": null,
"is_default": true,
"status": "active",
"created_at": "2026-01-15T12:30:00.000Z",
"updated_at": "2026-01-15T12:30:00.000Z"
}
],
"meta": {
"pagination": {
"page": 1,
"limit": 20,
"total": 1,
"total_pages": 1,
"has_next": false,
"has_prev": false
}
},
"request_id": "req_8f3c2a1c",
"timestamp": "2026-01-15T12:30:00.000Z"
}Los elementos de la lista devuelven el resumen del offer. Usa Obtener un offer por ID para recuperar los prices anidados.
GET/api/v1/offers/:id
Recupera un único offer, incluidos sus precios.
Auth: clave de Organization (con merchant_id) o clave de Merchant. Scope: offers:read.
curl https://api.tokeflow.com/api/v1/offers/ofr_a1b2c3d4e5 \
-H "Authorization: Bearer sk_live_mer_xxx"La respuesta 200 OK devuelve el objeto offer (con prices) envuelto en el sobre de éxito estándar. Se devuelve un 404 (not_found_error) si el offer no existe o pertenece a otro merchant.
PATCH/api/v1/offers/:id
Actualiza los campos mutables de un offer. Envía solo los campos que quieras cambiar.
Auth: clave de Organization (con merchant_id) o clave de Merchant. Scope: offers:write.
curl -X PATCH https://api.tokeflow.com/api/v1/offers/ofr_a1b2c3d4e5 \
-H "Authorization: Bearer sk_live_mer_xxx" \
-H "Content-Type: application/json" \
-d '{ "name": "Mensal Promo", "cycle_limit": 12 }'La respuesta 200 OK devuelve el objeto offer actualizado.
Para gestionar los precios adjuntos a un offer, usa los endpoints dedicados de Offer Prices. El PATCH del offer actualiza los campos a nivel de offer, no los precios anidados.
DELETE/api/v1/offers/:id
Elimina de forma lógica (soft-delete) un offer. La eliminación se propaga en cascada a los precios del offer. Los offers eliminados de forma lógica se pueden recuperar con restore.
Auth: clave de Organization (con merchant_id) o clave de Merchant. Scope: offers:write.
curl -X DELETE https://api.tokeflow.com/api/v1/offers/ofr_a1b2c3d4e5 \
-H "Authorization: Bearer sk_live_mer_xxx"Respuesta: 204 No Content (cuerpo vacío).
GET/api/v1/offers/:id/default-price
Resuelve el precio predeterminado de un offer. Pasa currency para obtener el precio predeterminado en esa moneda; la respuesta es null si no existe un precio coincidente.
Auth: clave de Organization (con merchant_id) o clave de Merchant. Scope: offers:read.
| Query param | Type | Required | Description |
|---|---|---|---|
currency | string | No | Código ISO 4217 para el que resolver el precio. |
curl "https://api.tokeflow.com/api/v1/offers/ofr_a1b2c3d4e5/default-price?currency=BRL" \
-H "Authorization: Bearer sk_live_mer_xxx"Respuesta 200 OK:
{
"success": true,
"data": {
"id": "opr_b1c2d3e4f5",
"offer_id": "ofr_a1b2c3d4e5",
"currency": "BRL",
"amount": 9900,
"first_charge_amount": 0,
"is_default": true,
"created_at": "2026-01-15T12:30:00.000Z",
"updated_at": "2026-01-15T12:30:00.000Z"
},
"request_id": "req_8f3c2a1d",
"timestamp": "2026-01-15T12:30:00.000Z"
}Cuando no coincide ningún precio predeterminado, data es null.
GET/api/v1/offers/:fromId/transitions/:toId/effective-behavior
Resuelve el change_charge_behavior efectivo para mover una suscripción de un offer a otro — por ejemplo, una mejora o una degradación. Ambos offers deben pertenecer a la misma familia de productos.
Orden de resolución:
- Si existe una transición activa para el par
fromId → toIdy establece unchange_charge_behavior, ese valor prevalece. - De lo contrario, se aplica el valor predeterminado de la familia de productos del offer de origen.
Auth: clave de Organization (con merchant_id) o clave de Merchant. Scope: offers:read.
change_charge_behavior | Significado |
|---|---|
next_renew | Aplicar el cambio en la próxima renovación. |
prorated | Prorratear la diferencia de inmediato. |
override | Reemplazar el calendario de cargos actual por el del nuevo offer. |
curl "https://api.tokeflow.com/api/v1/offers/ofr_from123/transitions/ofr_to456/effective-behavior" \
-H "Authorization: Bearer sk_live_mer_xxx"Respuesta 200 OK:
{
"success": true,
"data": {
"change_charge_behavior": "next_renew"
},
"request_id": "req_8f3c2a1e",
"timestamp": "2026-01-15T12:30:00.000Z"
}Se devuelve un 400 (validation_error) cuando el offer de origen no está en una familia de productos, o cuando los dos offers pertenecen a familias diferentes. Consulta Offer Transitions para crear y gestionar anulaciones de transición.
POST/api/v1/offers/:id/restore
Restaura un offer eliminado de forma lógica y sus precios.
Auth: clave de Organization (con merchant_id) o clave de Merchant. Scope: offers:write.
curl -X POST https://api.tokeflow.com/api/v1/offers/ofr_a1b2c3d4e5/restore \
-H "Authorization: Bearer sk_live_mer_xxx"La respuesta devuelve el objeto offer restaurado.
POST/api/v1/offers/:id/archive
Archiva un offer (establece status en archived). Los offers archivados permanecen en el catálogo y siguen siendo recuperables, pero no deberían ofrecerse para nuevas compras.
Auth: clave de Organization (con merchant_id) o clave de Merchant. Scope: offers:write.
curl -X POST https://api.tokeflow.com/api/v1/offers/ofr_a1b2c3d4e5/archive \
-H "Authorization: Bearer sk_live_mer_xxx"La respuesta devuelve el offer con status: "archived".
POST/api/v1/offers/:id/unarchive
Desarchiva un offer (vuelve a establecer status en active).
Auth: clave de Organization (con merchant_id) o clave de Merchant. Scope: offers:write.
curl -X POST https://api.tokeflow.com/api/v1/offers/ofr_a1b2c3d4e5/unarchive \
-H "Authorization: Bearer sk_live_mer_xxx"La respuesta devuelve el offer con status: "active".
Errores
Los endpoints de offers usan el sobre de error estándar. Casos comunes:
| HTTP | type | Cuándo |
|---|---|---|
| 400 | validation_error | Campos faltantes/inválidos, o una transición resuelta entre familias diferentes. |
| 401 | authentication_error | Clave de API faltante o inválida. |
| 403 | authorization_error | La clave carece del scope requerido. |
| 404 | not_found_error | Offer (o producto referenciado) no encontrado para este merchant. |
| 409 | conflict_error | Slug duplicado por producto, un segundo offer/precio predeterminado, o una moneda duplicada. |
| 429 | rate_limit_error | Límite de tasa excedido — espera y reintenta. |
Relacionados
- Products — aquello a lo que un offer pone precio.
- Offer Prices — gestiona precios por moneda en un offer.
- Offer Transitions — anula el comportamiento de cambio entre pares de offers.
- Product Families — agrupa productos y establece el comportamiento de cambio predeterminado.