Offers

Create and manage offers — the purchasable, priced configurations of a product, with billing cycles, trials, setup charges, and per-currency prices.

An offer is a purchasable configuration of a product. The product models what you sell; the offer models how it is purchased and billed: the billing cycle, the trial, the cycle limit, and one or more prices — one per currency.

A single product can carry many offers. A monthly plan and a yearly plan of the same product are two offers. A promo and the standard price are two offers. This separation lets you launch new pricing, run experiments, and localize amounts without touching the product itself.

Tokeflow is a payment orchestration platform, not a payment processor. Offers describe your pricing; the actual authorization, capture, and settlement of any charge happen at the connected payment provider. Catalog objects never move money.

How offers are billed

An offer's billing_cycle defines its cadence:

CycleMeaning
dailyRenews every day
biweeklyRenews every two weeks
monthlyRenews every month
quarterlyRenews every three months
half_yearlyRenews every six months
yearlyRenews every year
customRenews every custom_billing_days days (required when custom is used)
noneOn-demand, no scheduler — a one-time charge with no recurring renewal

The cycle_limit caps the number of charges. Omit it (or set null) to charge until the subscription is cancelled. When the limit is reached, the subscription expires — unless renew_after_cycle_limit is true, in which case a new subscription is created. If renewal_offer_id is set, that offer is used for the post-limit renewal; otherwise the same offer renews.

First-charge amount

Each price carries a base amount and an optional first_charge_amount. The first_charge_amount is only applied when the offer's setup_charge is true, and it sets the price of the first billing cycle — useful for an intro price, a one-time setup fee, or a discounted first month. A first_charge_amount of 0 means a card-validation charge (verify the instrument without charging).

Free trial

When free_trial is true, trial_days is required and defines the trial length in days. During the trial no recurring charge is made; billing begins after the trial ends.

amount and first_charge_amount are integer minor units (cents). R$99.00 = 9900; US$150.00 = 15000. Always pair an amount with its ISO 4217 currency.

A product may have at most one default offer (is_default = true), and an offer may have at most one default price (is_default = true). Within an offer, each currency may appear at most once.

The offer object

{
  "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"
    }
  ]
}

Attributes

FieldTypeDescription
idstringUnique identifier, prefixed ofr_.
product_idstringThe product this offer prices (prd_).
namestringHuman-readable name, e.g. "Mensal".
slugstringLowercase identifier, unique per product.
descriptionstring | nullOptional description.
billing_cycleenumdaily biweekly monthly quarterly half_yearly yearly custom none.
custom_billing_daysinteger | nullPeriod in days. Required when billing_cycle = custom.
cycle_limitinteger | nullFixed number of charges. null = charge until cancelled.
free_trialbooleanWhether the offer includes a free trial.
trial_daysinteger | nullTrial length in days. Required when free_trial = true.
setup_chargebooleanWhen true, the price's first_charge_amount is used for the first cycle.
renew_after_cycle_limitbooleanWhen true and cycle_limit is reached, a new subscription is created instead of expiring.
renewal_offer_idstring | nullOffer used for post-limit renewal (ofr_). null = renew with the same offer. Must belong to the same product family.
is_defaultbooleanWhether this is the default offer for the product.
statusenumactive or archived.
created_atstringISO 8601 UTC timestamp.
updated_atstringISO 8601 UTC timestamp.
pricesarrayNested offer prices (included on get-by-id).

The offer price object

FieldTypeDescription
idstringUnique identifier, prefixed opr_.
offer_idstringThe owning offer (ofr_).
currencystringISO 4217 currency code, e.g. BRL.
amountintegerRecurring price in minor units (cents).
first_charge_amountinteger | nullFirst-cycle price in minor units. Applied when setup_charge = true. 0 = card validation.
is_defaultbooleanWhether this is the default price for the offer.
created_atstringISO 8601 UTC timestamp.
updated_atstringISO 8601 UTC timestamp.

Endpoints

All offer endpoints are under https://api.tokeflow.com/api/v1. See Authentication for keys and scopes. Offers are a merchant-scoped resource: with an Organization key you must identify the target merchant via merchant_id; with a Merchant key the merchant is inferred.

POST/api/v1/offers

Create an offer together with at least one price.

Auth: Organization key (with merchant_id) or Merchant key. Scope: offers:write.

FieldTypeRequiredDescription
product_idstringYesProduct to attach the offer to (prd_).
namestringYesHuman-readable name.
slugstringYesLowercase identifier, unique per product.
billing_cycleenumYesOne of the billing cycle values.
statusenumYesactive or archived.
pricesarrayYesAt least one price object (see below).
descriptionstringNoOptional description.
custom_billing_daysintegerConditionalRequired when billing_cycle = custom.
cycle_limitintegerNoFixed number of charges. Omit = charge until cancelled.
free_trialbooleanNoDefaults to false.
trial_daysintegerConditionalRequired when free_trial = true.
setup_chargebooleanNoDefaults to false.
renew_after_cycle_limitbooleanNoDefaults to false.
renewal_offer_idstringNoOffer for post-limit renewal (ofr_).
is_defaultbooleanNoDefaults to false.

Each entry in prices:

FieldTypeRequiredDescription
currencystringYesISO 4217 code.
amountintegerYesRecurring price in minor units (≥ 0).
first_charge_amountintegerNoFirst-cycle price in minor units (≥ 0).
is_defaultbooleanNoMarks the default price for the 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 }
    ]
  }'

Response 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"
}

Pass an Idempotency-Key header (or idempotency_key in the body) on create calls. Replaying the same key returns the original result (200 OK) instead of creating a duplicate.

GET/api/v1/offers

List offers with pagination and filters.

Auth: Organization key (with merchant_id) or Merchant key. Scope: offers:read.

Query paramTypeDescription
pageintegerPage number, default 1, min 1.
limitintegerItems per page, default 20, max 100.
product_idstringFilter to a single product.
statusenumactive or archived.
billing_cycleenumOne of the billing cycle values.
is_defaultbooleanFilter to default offers only.
namestringPartial-match filter on offer name.
merchant_idstringRequired when using an Organization key.
curl "https://api.tokeflow.com/api/v1/offers?product_id=prd_a1b2c3d4e5&status=active&limit=20" \
  -H "Authorization: Bearer sk_live_mer_xxx"

Response 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"
}

List items return the offer summary. Use Get an offer by ID to retrieve the nested prices.

GET/api/v1/offers/:id

Retrieve a single offer, including its prices.

Auth: Organization key (with merchant_id) or Merchant key. Scope: offers:read.

curl https://api.tokeflow.com/api/v1/offers/ofr_a1b2c3d4e5 \
  -H "Authorization: Bearer sk_live_mer_xxx"

Response 200 OK returns the offer object (with prices) wrapped in the standard success envelope. A 404 (not_found_error) is returned if the offer does not exist or belongs to another merchant.

PATCH/api/v1/offers/:id

Update an offer's mutable fields. Send only the fields you want to change.

Auth: Organization key (with merchant_id) or Merchant key. 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 }'

Response 200 OK returns the updated offer object.

To manage the prices attached to an offer, use the dedicated Offer Prices endpoints. The offer PATCH updates offer-level fields, not nested prices.

DELETE/api/v1/offers/:id

Soft-delete an offer. The delete cascades to the offer's prices. Soft-deleted offers can be brought back with restore.

Auth: Organization key (with merchant_id) or Merchant key. Scope: offers:write.

curl -X DELETE https://api.tokeflow.com/api/v1/offers/ofr_a1b2c3d4e5 \
  -H "Authorization: Bearer sk_live_mer_xxx"

Response: 204 No Content (empty body).

GET/api/v1/offers/:id/default-price

Resolve the default price for an offer. Pass currency to get the default price in that currency; the response is null if no matching price exists.

Auth: Organization key (with merchant_id) or Merchant key. Scope: offers:read.

Query paramTypeRequiredDescription
currencystringNoISO 4217 code to resolve the price for.
curl "https://api.tokeflow.com/api/v1/offers/ofr_a1b2c3d4e5/default-price?currency=BRL" \
  -H "Authorization: Bearer sk_live_mer_xxx"

Response 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"
}

When no default price matches, data is null.

GET/api/v1/offers/:fromId/transitions/:toId/effective-behavior

Resolve the effective change_charge_behavior for moving a subscription from one offer to another — for example, an upgrade or downgrade. Both offers must belong to the same product family.

Resolution order:

  1. If an active transition exists for the fromId → toId pair and sets a change_charge_behavior, that value wins.
  2. Otherwise, the source offer's product-family default applies.

Auth: Organization key (with merchant_id) or Merchant key. Scope: offers:read.

change_charge_behaviorMeaning
next_renewApply the change at the next renewal.
proratedProrate the difference immediately.
overrideReplace the current charge schedule with the new offer's.
curl "https://api.tokeflow.com/api/v1/offers/ofr_from123/transitions/ofr_to456/effective-behavior" \
  -H "Authorization: Bearer sk_live_mer_xxx"

Response 200 OK:

{
  "success": true,
  "data": {
    "change_charge_behavior": "next_renew"
  },
  "request_id": "req_8f3c2a1e",
  "timestamp": "2026-01-15T12:30:00.000Z"
}

A 400 (validation_error) is returned when the source offer is not in a product family, or when the two offers belong to different families. See Offer Transitions to create and manage transition overrides.

POST/api/v1/offers/:id/restore

Restore a soft-deleted offer and its prices.

Auth: Organization key (with merchant_id) or Merchant key. Scope: offers:write.

curl -X POST https://api.tokeflow.com/api/v1/offers/ofr_a1b2c3d4e5/restore \
  -H "Authorization: Bearer sk_live_mer_xxx"

Response returns the restored offer object.

POST/api/v1/offers/:id/archive

Archive an offer (sets status to archived). Archived offers stay in the catalog and remain retrievable but should not be offered for new purchases.

Auth: Organization key (with merchant_id) or Merchant key. Scope: offers:write.

curl -X POST https://api.tokeflow.com/api/v1/offers/ofr_a1b2c3d4e5/archive \
  -H "Authorization: Bearer sk_live_mer_xxx"

Response returns the offer with status: "archived".

POST/api/v1/offers/:id/unarchive

Unarchive an offer (sets status back to active).

Auth: Organization key (with merchant_id) or Merchant key. Scope: offers:write.

curl -X POST https://api.tokeflow.com/api/v1/offers/ofr_a1b2c3d4e5/unarchive \
  -H "Authorization: Bearer sk_live_mer_xxx"

Response returns the offer with status: "active".

Errors

Offer endpoints use the standard error envelope. Common cases:

HTTPtypeWhen
400validation_errorMissing/invalid fields, or a transition resolved across different families.
401authentication_errorMissing or invalid API key.
403authorization_errorKey lacks the required scope.
404not_found_errorOffer (or referenced product) not found for this merchant.
409conflict_errorDuplicate slug per product, a second default offer/price, or a duplicate currency.
429rate_limit_errorRate limit exceeded — back off and retry.

On this page