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_id → to_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:
| Header | Type | Required | Description |
|---|---|---|---|
x-merchant-id | string | Yes | The 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
| Field | Type | Description |
|---|---|---|
id | string | Unique identifier, prefixed oft_. |
from_offer_id | string | The offer the subscription is moving away from. Prefixed ofr_. |
to_offer_id | string | The offer the subscription is moving to. Prefixed ofr_. Must belong to the same product family as from_offer_id. |
change_charge_behavior | enum | null | Per-pair override of the product family's change_charge_behavior — next_renew, prorated, or override. null means fall back to the family default. |
is_active | boolean | Whether the transition is active (eligible for processing). Inactive transitions are ignored and the family default applies. |
created_at | string | ISO 8601 UTC creation timestamp. |
updated_at | string | ISO 8601 UTC last-update timestamp. |
The three change_charge_behavior values:
| Value | Meaning |
|---|---|
next_renew | Apply the change at the next renewal — the new price takes effect on the following billing cycle. |
prorated | Prorate the difference for the current cycle. |
override | Replace 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
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
| Field | Type | Required | Description |
|---|---|---|---|
page | number | No | Page number, 1-indexed. Default 1. |
limit | number | No | Items per page. Default 20, min 1, max 100. |
from_offer_id | string | No | Filter transitions originating from a specific offer. |
to_offer_id | string | No | Filter transitions targeting a specific offer. |
is_active | boolean | No | Filter to active transitions only (pass true). |
change_charge_behavior | string | No | Filter 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 response — 200 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
| Field | Type | Description |
|---|---|---|
page | number | Current page number. |
limit | number | Number of items per page. |
total | number | Total number of items. |
total_pages | number | Total number of pages. |
has_next | boolean | Whether there is a next page. |
has_prev | boolean | Whether there is a previous page. |
POST/api/v1/merchant/offer-transitions
Auth: Dashboard session (JWT bearer). Merchant membership required.
Creates an offer transition for a from_offer_id → to_offer_id pair. Both offers must belong to the same product family.
Request fields
| Field | Type | Required | Description |
|---|---|---|---|
from_offer_id | string | Yes | Offer the subscription is moving away from, prefixed ofr_. |
to_offer_id | string | Yes | Offer the subscription is moving to, prefixed ofr_. Must belong to the same product family as from_offer_id. |
change_charge_behavior | string | No | Overrides 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_active | boolean | No | Whether 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 response — 201 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
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 response — 200 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
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_id → to_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 response — 200 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
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 response — 204 No Content
The response has no body.
Errors
| HTTP | Typical cause |
|---|---|
400 | Invalid body or query — to_offer_id outside the from_offer_id product family, or an unknown change_charge_behavior value. |
401 | Missing, invalid, or expired session token — sign in again. |
403 | The session's account is not a member of the merchant in x-merchant-id. |
404 | No transition with that id for this merchant. |
See Errors for the error format used across the API.
Related
- Offer transitions (public API) — the same resource over
sk_API keys, with the full precedence model. - Product families — where the default
change_charge_behaviorlives. - Offers — the pairs a transition connects, including the effective-behavior resolver.
- Subscriptions — the plan changes these rules govern.