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:

CicloSignificado
dailySe renueva cada día
biweeklySe renueva cada dos semanas
monthlySe renueva cada mes
quarterlySe renueva cada tres meses
half_yearlySe renueva cada seis meses
yearlySe renueva cada año
customSe renueva cada custom_billing_days días (obligatorio cuando se usa custom)
noneBajo 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

FieldTypeDescription
idstringIdentificador único, con prefijo ofr_.
product_idstringEl producto al que este offer pone precio (prd_).
namestringNombre legible, p. ej. "Mensal".
slugstringIdentificador en minúsculas, único por producto.
descriptionstring | nullDescripción opcional.
billing_cycleenumdaily biweekly monthly quarterly half_yearly yearly custom none.
custom_billing_daysinteger | nullPeriodo en días. Obligatorio cuando billing_cycle = custom.
cycle_limitinteger | nullNúmero fijo de cargos. null = cobrar hasta la cancelación.
free_trialbooleanSi el offer incluye un periodo de prueba gratuito.
trial_daysinteger | nullDuración del periodo de prueba en días. Obligatorio cuando free_trial = true.
setup_chargebooleanCuando es true, el first_charge_amount del precio se usa para el primer ciclo.
renew_after_cycle_limitbooleanCuando es true y se alcanza el cycle_limit, se crea una nueva suscripción en lugar de expirar.
renewal_offer_idstring | nullOffer 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_defaultbooleanSi este es el offer predeterminado del producto.
statusenumactive o archived.
created_atstringMarca de tiempo ISO 8601 UTC.
updated_atstringMarca de tiempo ISO 8601 UTC.
pricesarrayPrecios del offer anidados (incluidos en la obtención por ID).

El objeto precio del offer

FieldTypeDescription
idstringIdentificador único, con prefijo opr_.
offer_idstringEl offer propietario (ofr_).
currencystringCódigo de moneda ISO 4217, p. ej. BRL.
amountintegerPrecio recurrente en unidades menores (centavos).
first_charge_amountinteger | nullPrecio 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_defaultbooleanSi este es el precio predeterminado del offer.
created_atstringMarca de tiempo ISO 8601 UTC.
updated_atstringMarca 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.

FieldTypeRequiredDescription
product_idstringSíProducto al que adjuntar el offer (prd_).
namestringSíNombre legible.
slugstringSíIdentificador en minúsculas, único por producto.
billing_cycleenumSíUno de los valores de ciclo de facturación.
statusenumSíactive o archived.
pricesarraySíAl menos un objeto de precio (ver abajo).
descriptionstringNoDescripción opcional.
custom_billing_daysintegerCondicionalObligatorio cuando billing_cycle = custom.
cycle_limitintegerNoNúmero fijo de cargos. Omitir = cobrar hasta la cancelación.
free_trialbooleanNoPor defecto false.
trial_daysintegerCondicionalObligatorio cuando free_trial = true.
setup_chargebooleanNoPor defecto false.
renew_after_cycle_limitbooleanNoPor defecto false.
renewal_offer_idstringNoOffer para la renovación posterior al límite (ofr_).
is_defaultbooleanNoPor defecto false.

Cada entrada en prices:

FieldTypeRequiredDescription
currencystringSíCódigo ISO 4217.
amountintegerSíPrecio recurrente en unidades menores (≥ 0).
first_charge_amountintegerNoPrecio del primer ciclo en unidades menores (≥ 0).
is_defaultbooleanNoMarca 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 paramTypeDescription
pageintegerNúmero de página, por defecto 1, mínimo 1.
limitintegerElementos por página, por defecto 20, máximo 100.
product_idstringFiltrar a un único producto.
statusenumactive o archived.
billing_cycleenumUno de los valores de ciclo de facturación.
is_defaultbooleanFiltrar solo a los offers predeterminados.
namestringFiltro de coincidencia parcial sobre el nombre del offer.
merchant_idstringObligatorio 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 paramTypeRequiredDescription
currencystringNoCó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:

  1. Si existe una transición activa para el par fromId → toId y establece un change_charge_behavior, ese valor prevalece.
  2. 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_behaviorSignificado
next_renewAplicar el cambio en la próxima renovación.
proratedProrratear la diferencia de inmediato.
overrideReemplazar 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:

HTTPtypeCuándo
400validation_errorCampos faltantes/inválidos, o una transición resuelta entre familias diferentes.
401authentication_errorClave de API faltante o inválida.
403authorization_errorLa clave carece del scope requerido.
404not_found_errorOffer (o producto referenciado) no encontrado para este merchant.
409conflict_errorSlug duplicado por producto, un segundo offer/precio predeterminado, o una moneda duplicada.
429rate_limit_errorLí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.

En esta página