Merchant offer transitions

Manage per-pair billing rules for plan changes from the merchant dashboard — list, create, update, and delete offer transitions with a dashboard session.

An offer transition is a per-pair billing rule for plan changes. It pins one specific from_offer_idto_offer_id move — an upgrade, a downgrade, or a plan switch within the same product family — to a billing rule (change_charge_behavior) and an on/off toggle (is_active). When a subscription changes between that exact pair of offers, the transition's rule takes precedence over the product family's default.

These endpoints are the surface behind the dashboard's catalog screens: the transitions table, the create dialog, and the activate/deactivate and delete controls. They operate on the same underlying transition rows as the public offer transitions API — a rule created here is immediately visible there, and vice versa.

Dashboard endpoints power the Tokeflow Dashboard UI. They are session-authenticated: sign in to obtain a JWT and send it as a bearer token. They do not accept sk_ or pk_ API keys — for programmatic catalog automation, use the public offer transitions API instead.

Every endpoint on this page also requires the x-merchant-id header:

HeaderTypeRequiredDescription
x-merchant-idstringYesThe merchant whose catalog you are managing, prefixed mrc_. Selects the active merchant context when your account belongs to more than one merchant.

How a transition takes effect

Creating, deactivating, or deleting a transition changes which billing rule applies the next time a subscription moves between the pair:

An inactive or deleted transition never blocks a plan change — the move simply falls back to the product family's default change_charge_behavior. Deactivating (PATCH) is reversible; deleting is not.

The transition engine executes all three behaviors — next_renew, prorated, and override. The change_charge_behavior you set on a pair is the one applied when a subscription moves between those offers.

The offer transition object

FieldTypeDescription
idstringUnique identifier, prefixed oft_.
from_offer_idstringThe offer the subscription is moving away from. Prefixed ofr_.
to_offer_idstringThe offer the subscription is moving to. Prefixed ofr_. Must belong to the same product family as from_offer_id.
change_charge_behaviorenum | nullPer-pair override of the product family's change_charge_behaviornext_renew, prorated, or override. null means fall back to the family default.
is_activebooleanWhether the transition is active (eligible for processing). Inactive transitions are ignored and the family default applies.
created_atstringISO 8601 UTC creation timestamp.
updated_atstringISO 8601 UTC last-update timestamp.

The three change_charge_behavior values:

ValueMeaning
next_renewApply the change at the next renewal — the new price takes effect on the following billing cycle.
proratedProrate the difference for the current cycle.
overrideReplace the current charge with the new offer's terms immediately.

Dashboard endpoints return the object directly — there is no success/data envelope around single-resource responses.


Endpoints

GET/api/v1/merchant/offer-transitions

Merchant

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

Returns a paginated list of the merchant's offer transitions. This is the query behind the dashboard's transitions table, including its offer and status filters.

Query parameters

FieldTypeRequiredDescription
pagenumberNoPage number, 1-indexed. Default 1.
limitnumberNoItems per page. Default 20, min 1, max 100.
from_offer_idstringNoFilter transitions originating from a specific offer.
to_offer_idstringNoFilter transitions targeting a specific offer.
is_activebooleanNoFilter to active transitions only (pass true).
change_charge_behaviorstringNoFilter by billing rule — next_renew, prorated, or override.

Example request

curl -G https://api.tokeflow.com/api/v1/merchant/offer-transitions \
  -H "Authorization: Bearer eyJhbGciOiJIUzI1NiIs…" \
  -H "x-merchant-id: mrc_123" \
  --data-urlencode "page=1" \
  --data-urlencode "limit=20" \
  --data-urlencode "is_active=true"

Example response200 OK

{
  "data": [
    {
      "id": "oft_a1b2c3d4e5",
      "from_offer_id": "ofr_3hk9d2",
      "to_offer_id": "ofr_7pq4m1",
      "change_charge_behavior": "next_renew",
      "is_active": true,
      "created_at": "2026-05-19T12:00:00Z",
      "updated_at": "2026-05-19T12:00:00Z"
    },
    {
      "id": "oft_f6g7h8i9j0",
      "from_offer_id": "ofr_7pq4m1",
      "to_offer_id": "ofr_3hk9d2",
      "change_charge_behavior": null,
      "is_active": true,
      "created_at": "2026-06-02T15:45:00Z",
      "updated_at": "2026-06-02T15:45:00Z"
    }
  ],
  "meta": {
    "page": 1,
    "limit": 20,
    "total": 2,
    "total_pages": 1,
    "has_next": false,
    "has_prev": false
  }
}

The second transition in the example sets change_charge_behavior to null — the row exists for its is_active toggle, and the billing rule falls back to the family default.

Pagination meta

FieldTypeDescription
pagenumberCurrent page number.
limitnumberNumber of items per page.
totalnumberTotal number of items.
total_pagesnumberTotal number of pages.
has_nextbooleanWhether there is a next page.
has_prevbooleanWhether there is a previous page.

POST/api/v1/merchant/offer-transitions

Merchant

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

Creates an offer transition for a from_offer_idto_offer_id pair. Both offers must belong to the same product family.

Request fields

FieldTypeRequiredDescription
from_offer_idstringYesOffer the subscription is moving away from, prefixed ofr_.
to_offer_idstringYesOffer the subscription is moving to, prefixed ofr_. Must belong to the same product family as from_offer_id.
change_charge_behaviorstringNoOverrides the product family's change_charge_behavior for this pair — next_renew, prorated, or override. Omit or send null to fall back to the family default. All three behaviors are processed by the engine.
is_activebooleanNoWhether this transition is active (eligible for processing). Defaults to true.

Example request

curl -X POST https://api.tokeflow.com/api/v1/merchant/offer-transitions \
  -H "Authorization: Bearer eyJhbGciOiJIUzI1NiIs…" \
  -H "x-merchant-id: mrc_123" \
  -H "Content-Type: application/json" \
  -d '{
    "from_offer_id": "ofr_3hk9d2",
    "to_offer_id": "ofr_7pq4m1",
    "change_charge_behavior": "next_renew",
    "is_active": true
  }'

Example response201 Created

{
  "id": "oft_a1b2c3d4e5",
  "from_offer_id": "ofr_3hk9d2",
  "to_offer_id": "ofr_7pq4m1",
  "change_charge_behavior": "next_renew",
  "is_active": true,
  "created_at": "2026-05-19T12:00:00Z",
  "updated_at": "2026-05-19T12:00:00Z"
}

A 400 is returned when to_offer_id does not belong to the same product family as from_offer_id, or when change_charge_behavior is not one of the three allowed values.


GET/api/v1/merchant/offer-transitions/:id

Merchant

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

Retrieves a single offer transition by ID. Returns the offer transition object.

Example request

curl https://api.tokeflow.com/api/v1/merchant/offer-transitions/oft_a1b2c3d4e5 \
  -H "Authorization: Bearer eyJhbGciOiJIUzI1NiIs…" \
  -H "x-merchant-id: mrc_123"

Example response200 OK

{
  "id": "oft_a1b2c3d4e5",
  "from_offer_id": "ofr_3hk9d2",
  "to_offer_id": "ofr_7pq4m1",
  "change_charge_behavior": "next_renew",
  "is_active": true,
  "created_at": "2026-05-19T12:00:00Z",
  "updated_at": "2026-05-19T12:00:00Z"
}

A 404 is returned if the transition does not exist or does not belong to the merchant in x-merchant-id.


PATCH/api/v1/merchant/offer-transitions/:id

Merchant

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

Updates an offer transition. The payload is a partial object — send only the fields you want to change. The most common updates are toggling is_active (the dashboard's activate/deactivate control) and adjusting change_charge_behavior, including setting it to null to fall back to the family default.

To repoint a rule at a different offer pair, prefer deleting the transition and creating a new one — the from_offer_idto_offer_id pair is the transition's identity.

Example request — deactivate a transition

curl -X PATCH https://api.tokeflow.com/api/v1/merchant/offer-transitions/oft_a1b2c3d4e5 \
  -H "Authorization: Bearer eyJhbGciOiJIUzI1NiIs…" \
  -H "x-merchant-id: mrc_123" \
  -H "Content-Type: application/json" \
  -d '{ "is_active": false }'

Example response200 OK

{
  "id": "oft_a1b2c3d4e5",
  "from_offer_id": "ofr_3hk9d2",
  "to_offer_id": "ofr_7pq4m1",
  "change_charge_behavior": "next_renew",
  "is_active": false,
  "created_at": "2026-05-19T12:00:00Z",
  "updated_at": "2026-08-12T09:30:00Z"
}

While is_active is false, the pair bills by the product family's default change_charge_behavior. Re-activate at any time with { "is_active": true }.


DELETE/api/v1/merchant/offer-transitions/:id

Merchant

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

Deletes an offer transition. From then on, plan changes between the pair follow the product family's default change_charge_behavior.

Deletion is permanent. If you only want to pause a rule — keeping its configuration for later — deactivate it with PATCH and { "is_active": false } instead.

Example request

curl -X DELETE https://api.tokeflow.com/api/v1/merchant/offer-transitions/oft_a1b2c3d4e5 \
  -H "Authorization: Bearer eyJhbGciOiJIUzI1NiIs…" \
  -H "x-merchant-id: mrc_123"

Example response204 No Content

The response has no body.


Errors

HTTPTypical cause
400Invalid body or query — to_offer_id outside the from_offer_id product family, or an unknown change_charge_behavior value.
401Missing, invalid, or expired session token — sign in again.
403The session's account is not a member of the merchant in x-merchant-id.
404No transition with that id for this merchant.

See Errors for the error format used across the API.

On this page