Merchant offers

Create, list, update, archive, restore, and soft-delete offers — the pricing plans attached to a product — from the Tokeflow Dashboard merchant surface.

An offer is a purchasable plan attached to a product: it defines the billing cycle, trial and setup behavior, cycle limits, and one or more currency-specific prices. A product can carry several offers — a monthly plan, a yearly plan, a promotional variant — and each offer carries its own prices in minor units per ISO 4217 currency. Offers are what customers actually subscribe to or buy at checkout.

These endpoints power the offer management screens of the Tokeflow Dashboard for a single merchant. Offers reference a product and are referenced in turn by subscriptions, which is why deletion is guarded: an offer with any subscription history is preserved for billing records.

These endpoints power the Tokeflow Dashboard UI. They are authenticated with a dashboard session (JWT bearer) obtained by signing in — not with sk_/pk_ API keys. Every request must also send the x-merchant-id header identifying the merchant context (mrc_…). See Dashboard authentication.

Offer lifecycle

Archiving hides an offer from new checkouts while keeping it fully readable; it is reversible with unarchive. Soft-deletion removes the offer (and cascades to its prices) but is refused with 409 while any subscription — in any status — still references it, and can be reversed with restore.

The offer object

Derived from OfferResponseDto, returned by every endpoint on this page that returns a body.

FieldTypeDescription
idstringOffer ID, prefixed ofr_.
product_idstringParent product, prefixed prd_.
namestringDisplay name of the offer.
slugstringLowercase identifier, unique per product.
descriptionstring | nullFree-text description.
billing_cyclestringOne of daily, biweekly, monthly, quarterly, half_yearly, yearly, custom, none.
custom_billing_daysnumber | nullPeriod in days. Required when billing_cycle = custom.
cycle_limitnumber | nullFixed number of charges. null = charge until cancelled.
free_trialbooleanWhether the offer starts with a free trial.
trial_daysnumber | nullTrial duration in days. Required when free_trial = true.
setup_chargebooleanWhether the first cycle is billed at a distinct first-charge amount.
renew_after_cycle_limitbooleanWhether the subscription renews after cycle_limit is reached.
renewal_offer_idstring | nullOffer used for post-cycle_limit renewal. null = renew with the same offer.
is_defaultbooleanWhether this is the default offer for its product.
statusstringactive or archived.
created_atstringCreation timestamp (ISO 8601 UTC).
updated_atstringLast update timestamp (ISO 8601 UTC).
pricesarrayOffer prices. Populated when prices are eager-loaded (create, get by ID).

billing_cycle: "none" describes a one-time offer with no recurrence. For recurring cycles that do not fit the presets, use custom together with custom_billing_days.


Endpoints

GET/api/v1/merchant/offers

Merchant

Auth: Dashboard session (JWT bearer). Merchant membership required.

Returns a paginated list of the merchant's offers, filterable by product, status, billing cycle, and name.

Query parameters

FieldTypeRequiredDescription
pagenumberNoPage number, 1-indexed (default 1, min 1).
limitnumberNoItems per page (default 20, min 1, max 100).
product_idstringNoFilter offers to a single product.
statusstringNoFilter by status: active or archived.
billing_cyclestringNoFilter by billing cycle: daily, biweekly, monthly, quarterly, half_yearly, yearly, custom, or none.
is_defaultbooleanNoFilter to default offers only.
namestringNoPartial-match filter on offer name.

Example request

curl -G https://api.tokeflow.com/api/v1/merchant/offers \
  -H "Authorization: Bearer <session-jwt>" \
  -H "x-merchant-id: mrc_9f8e7d6c5b" \
  --data-urlencode "product_id=prd_a1b2c3d4e5" \
  --data-urlencode "status=active" \
  --data-urlencode "page=1" \
  --data-urlencode "limit=20"

Example response200 OK

{
  "data": [
    {
      "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": false,
      "renew_after_cycle_limit": false,
      "renewal_offer_id": null,
      "is_default": true,
      "status": "active",
      "created_at": "2026-05-19T12:00:00Z",
      "updated_at": "2026-05-19T12:00:00Z"
    }
  ],
  "meta": {
    "page": 1,
    "limit": 20,
    "total": 42,
    "total_pages": 3,
    "has_next": true,
    "has_prev": false
  }
}

POST/api/v1/merchant/offers

Merchant

Auth: Dashboard session (JWT bearer). Merchant membership required.

Creates an offer with its initial prices. At least one price is required; each price is an amount in minor units for one ISO 4217 currency.

Request fields

FieldTypeRequiredDescription
product_idstringYesParent product, prefixed prd_.
namestringYesDisplay name (max 255 characters).
slugstringYesLowercase identifier, unique per product (max 100 characters).
descriptionstringNoFree-text description (max 2000 characters).
billing_cyclestringYesOne of daily, biweekly, monthly, quarterly, half_yearly, yearly, custom, none.
custom_billing_daysnumberConditionalPeriod in days (min 1). Required when billing_cycle = custom.
cycle_limitnumberNoFixed number of charges (min 1). Omit or null = charge until cancelled.
free_trialbooleanNoWhether the offer starts with a free trial. Default false.
trial_daysnumberConditionalTrial duration in days (min 1). Required when free_trial = true.
setup_chargebooleanNoBill the first cycle at each price's first_charge_amount. Default false.
renew_after_cycle_limitbooleanNoRenew the subscription after cycle_limit charges. Default false.
renewal_offer_idstringNoOffer for post-cycle_limit renewal. Omit = renew with the same offer.
is_defaultbooleanNoMake this the default offer for its product. Default false.
statusstringYesactive or archived.
pricesarrayYesAt least one price (see below).

Price fields (prices[])

FieldTypeRequiredDescription
currencystringYesISO 4217 currency code.
amountnumberYesAmount in minor units (min 0). E.g. R$99.00 = 9900.
first_charge_amountnumberNoFirst-cycle amount in minor units. Used when setup_charge = true. 0 = card validation only.
is_defaultbooleanNoMark this currency as the offer's default.

Example request

curl -X POST https://api.tokeflow.com/api/v1/merchant/offers \
  -H "Authorization: Bearer <session-jwt>" \
  -H "x-merchant-id: mrc_9f8e7d6c5b" \
  -H "Content-Type: application/json" \
  -d '{
    "product_id": "prd_a1b2c3d4e5",
    "name": "Mensal",
    "slug": "mensal",
    "description": "Plano mensal padrão",
    "billing_cycle": "monthly",
    "cycle_limit": 12,
    "free_trial": false,
    "setup_charge": false,
    "is_default": true,
    "status": "active",
    "prices": [
      { "currency": "BRL", "amount": 9900, "is_default": true },
      { "currency": "USD", "amount": 1900 }
    ]
  }'

Example response201 Created

{
  "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": false,
  "renew_after_cycle_limit": false,
  "renewal_offer_id": null,
  "is_default": true,
  "status": "active",
  "created_at": "2026-05-19T12:00:00Z",
  "updated_at": "2026-05-19T12:00:00Z",
  "prices": [
    {
      "currency": "BRL",
      "amount": 9900,
      "is_default": true
    },
    {
      "currency": "USD",
      "amount": 1900,
      "is_default": false
    }
  ]
}

A free trial does not need a setup charge: free_trial: true with trial_days charges 0 up front on its own, and the card is still enrolled by a card validation. To take a joining fee alongside the trial, add setup_charge: true with a first_charge_amount above 0 on the price; first_charge_amount: 0 is the explicit way to spell "validate the card, charge nothing".


GET/api/v1/merchant/offers/:id

Merchant

Auth: Dashboard session (JWT bearer). Merchant membership required.

Retrieves a single offer by ID, including its prices. Returns the offer object.

Example request

curl https://api.tokeflow.com/api/v1/merchant/offers/ofr_a1b2c3d4e5 \
  -H "Authorization: Bearer <session-jwt>" \
  -H "x-merchant-id: mrc_9f8e7d6c5b"

Example response200 OK

{
  "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": false,
  "renew_after_cycle_limit": false,
  "renewal_offer_id": null,
  "is_default": true,
  "status": "active",
  "created_at": "2026-05-19T12:00:00Z",
  "updated_at": "2026-05-19T12:00:00Z",
  "prices": [
    {
      "currency": "BRL",
      "amount": 9900,
      "is_default": true
    }
  ]
}

PATCH/api/v1/merchant/offers/:id

Merchant

Auth: Dashboard session (JWT bearer). Merchant membership required.

Updates an offer. Send only the fields you want to change; the updated offer object is returned.

Example request

curl -X PATCH https://api.tokeflow.com/api/v1/merchant/offers/ofr_a1b2c3d4e5 \
  -H "Authorization: Bearer <session-jwt>" \
  -H "x-merchant-id: mrc_9f8e7d6c5b" \
  -H "Content-Type: application/json" \
  -d '{ "name": "Mensal Plus", "description": "Plano mensal com upgrade" }'

Example response200 OK

{
  "id": "ofr_a1b2c3d4e5",
  "product_id": "prd_a1b2c3d4e5",
  "name": "Mensal Plus",
  "slug": "mensal",
  "description": "Plano mensal com upgrade",
  "billing_cycle": "monthly",
  "custom_billing_days": null,
  "cycle_limit": 12,
  "free_trial": false,
  "trial_days": null,
  "setup_charge": false,
  "renew_after_cycle_limit": false,
  "renewal_offer_id": null,
  "is_default": true,
  "status": "active",
  "created_at": "2026-05-19T12:00:00Z",
  "updated_at": "2026-05-19T14:30:00Z"
}

DELETE/api/v1/merchant/offers/:id

Merchant

Auth: Dashboard session (JWT bearer). Merchant membership required.

Soft-deletes an offer, cascading to its prices. The request is refused with 409 while any subscription — in any status — references the offer; offers referenced by subscriptions are kept for billing history. A soft-deleted offer can be brought back with restore.

Response codes

CodeWhen
204 No ContentOffer soft-deleted.
404 Not FoundOffer not found.
409 ConflictOffer has subscriptions and cannot be deleted.

Example request

curl -X DELETE https://api.tokeflow.com/api/v1/merchant/offers/ofr_a1b2c3d4e5 \
  -H "Authorization: Bearer <session-jwt>" \
  -H "x-merchant-id: mrc_9f8e7d6c5b"

Example response204 No Content (empty body)

When an offer cannot be deleted because subscriptions reference it, archive it instead — archived offers stay out of new checkouts while their billing history remains intact.


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

Merchant

Auth: Dashboard session (JWT bearer). Merchant membership required.

Archives an offer, setting status to archived. Returns the updated offer object.

Example request

curl -X POST https://api.tokeflow.com/api/v1/merchant/offers/ofr_a1b2c3d4e5/archive \
  -H "Authorization: Bearer <session-jwt>" \
  -H "x-merchant-id: mrc_9f8e7d6c5b"

Example response201 Created

{
  "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": false,
  "renew_after_cycle_limit": false,
  "renewal_offer_id": null,
  "is_default": false,
  "status": "archived",
  "created_at": "2026-05-19T12:00:00Z",
  "updated_at": "2026-05-19T15:00:00Z"
}

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

Merchant

Auth: Dashboard session (JWT bearer). Merchant membership required.

Unarchives an offer, setting status back to active. Returns the updated offer object.

Example request

curl -X POST https://api.tokeflow.com/api/v1/merchant/offers/ofr_a1b2c3d4e5/unarchive \
  -H "Authorization: Bearer <session-jwt>" \
  -H "x-merchant-id: mrc_9f8e7d6c5b"

Example response201 Created

{
  "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": false,
  "renew_after_cycle_limit": false,
  "renewal_offer_id": null,
  "is_default": false,
  "status": "active",
  "created_at": "2026-05-19T12:00:00Z",
  "updated_at": "2026-05-19T15:10:00Z"
}

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

Merchant

Auth: Dashboard session (JWT bearer). Merchant membership required.

Restores a soft-deleted offer and its prices. Returns the restored offer object.

Example request

curl -X POST https://api.tokeflow.com/api/v1/merchant/offers/ofr_a1b2c3d4e5/restore \
  -H "Authorization: Bearer <session-jwt>" \
  -H "x-merchant-id: mrc_9f8e7d6c5b"

Example response201 Created

{
  "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": false,
  "renew_after_cycle_limit": false,
  "renewal_offer_id": null,
  "is_default": false,
  "status": "active",
  "created_at": "2026-05-19T12:00:00Z",
  "updated_at": "2026-05-19T16:00:00Z"
}

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

Merchant

Auth: Dashboard session (JWT bearer). Merchant membership required.

Returns the default price of an offer for a given currency, or null if the offer has no default price in that currency.

Query parameters

FieldTypeRequiredDescription
currencystringYesISO 4217 currency code to resolve the default price for.

Example request

curl -G https://api.tokeflow.com/api/v1/merchant/offers/ofr_a1b2c3d4e5/default-price \
  -H "Authorization: Bearer <session-jwt>" \
  -H "x-merchant-id: mrc_9f8e7d6c5b" \
  --data-urlencode "currency=BRL"

Example response200 OK

{
  "currency": "BRL",
  "amount": 9900,
  "is_default": true
}

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

Merchant

Auth: Dashboard session (JWT bearer). Merchant membership required.

Resolves the effective change_charge_behavior for an offer transition pair — how a subscription moving from offer :fromId to offer :toId will be charged. Use it to preview the charge behavior the Dashboard will apply before confirming a plan change.

Example request

curl https://api.tokeflow.com/api/v1/merchant/offers/ofr_a1b2c3d4e5/transitions/ofr_f6g7h8i9j0/effective-behavior \
  -H "Authorization: Bearer <session-jwt>" \
  -H "x-merchant-id: mrc_9f8e7d6c5b"

Example response200 OK


Errors

Common error responses for this resource:

HTTPTypical cause
400Invalid body — e.g. missing prices, billing_cycle: "custom" without custom_billing_days, or free_trial: true without trial_days.
401Missing or expired dashboard session.
403The session's user is not a member of the merchant in x-merchant-id.
404Offer not found or not owned by the merchant.
409Delete refused — the offer is referenced by at least one subscription.

On this page