Offer Prices
Attach amounts and currencies to an offer with offer prices — one default per currency, with soft-delete and restore for safe lifecycle management.
An offer price attaches a concrete amount, in a specific currency, to an offer. An offer can carry several prices — one per currency — so the same plan can be sold in BRL, USD, EUR, and more. Exactly one price per offer can be marked as the default for that offer.
Prices are the leaf of the catalog. The offer defines how a plan bills (cycle, trial, setup); the price defines how much it costs in each currency.
Tokeflow is a payment orchestration platform, not a payment processor. Offer prices model what a plan costs; the actual authorization, capture, and settlement of any charge happen at the connected payment provider. Catalog objects never move money.
How pricing works
- Amounts are integer minor units. Store cents, not decimals.
R$99.00is9900;US$150.00is15000. Always pair anamountwith an ISO 4217currency. - One price per currency, per offer. An offer cannot have two live prices in the same currency. Creating or updating a price into a currency that is already taken returns
409 Conflict. - One default per offer. At most one price on an offer can have
is_default: true. The default is the price Tokeflow surfaces first (and lists first) for that offer. first_charge_amountdrives setup charges. When the parent offer hassetup_charge = true, the first cycle is billed atfirst_charge_amountinstead ofamount. A value of0means a card-validation-only first charge (for example, a free trial that verifies the card). Leave itnullto bill the standardamounton the first cycle.
List endpoints sort prices with the default first (is_default DESC), then by currency ascending — so the price you most often need is at the top of the response.
The offer price object
{
"id": "opr_b1c2d3e4f5",
"offer_id": "ofr_a1b2c3d4e5",
"currency": "BRL",
"amount": 9900,
"first_charge_amount": 0,
"is_default": true,
"created_at": "2026-05-19T12:00:00.000Z",
"updated_at": "2026-05-19T12:00:00.000Z"
}| Field | Type | Description |
|---|---|---|
id | string | Unique offer price ID, prefixed opr_. Opaque — never parse it. |
offer_id | string | The offer this price belongs to (ofr_). |
currency | string | ISO 4217 currency code, e.g. "BRL". Unique per offer among live prices. |
amount | integer | Recurring/standard amount in minor units (cents). |
first_charge_amount | integer | null | First-cycle amount in minor units, used when the offer has setup_charge = true. 0 = card-validation-only. null = bill the standard amount. |
is_default | boolean | Whether this is the default price for the offer. At most one per offer. |
created_at | string | ISO 8601 UTC creation timestamp. |
updated_at | string | ISO 8601 UTC last-update timestamp. |
Endpoints
All offer price endpoints live under https://api.tokeflow.com/api/v1. Authentication uses a secret key — see Authentication.
| Method | Path | Purpose |
|---|---|---|
POST | /offers/:offerId/prices | Add a price to an offer |
GET | /offers/:offerId/prices | List the prices on an offer |
GET | /offer-prices/:id | Retrieve a single price |
PATCH | /offer-prices/:id | Update a price |
DELETE | /offer-prices/:id | Soft-delete a price |
POST | /offer-prices/:id/restore | Restore a soft-deleted price |
Offer prices require the offers:read scope for reads and offers:write for mutations. With an Organization key, scope the request to a merchant via the merchant_id query parameter; a Merchant key infers the merchant automatically. See Authentication.
POST/api/v1/offers/:offerId/prices
Auth: Organization key (with merchant_id) or Merchant key.
Adds a price to an existing offer. The parent offer must belong to the authenticated merchant.
Path parameters
| Field | Type | Required | Description |
|---|---|---|---|
offerId | string | Yes | The offer to price (ofr_). |
Body parameters
| Field | Type | Required | Description |
|---|---|---|---|
currency | string | Yes | ISO 4217 code (exactly 3 characters). Must be unique among the offer's live prices. |
amount | integer | Yes | Standard amount in minor units. Minimum 0. |
first_charge_amount | integer | No | First-cycle amount in minor units (minimum 0). Used when the offer has setup_charge = true. Omit to bill the standard amount. |
is_default | boolean | No | Mark this price as the offer's default. Defaults to false. The offer may have at most one default. |
curl -X POST https://api.tokeflow.com/api/v1/offers/ofr_a1b2c3d4e5/prices \
-H "Authorization: Bearer sk_live_mer_REPLACE_ME" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: 7c3b9f1e-2a4d-4c8e-9f1a-6b2d8e0c4a11" \
-d '{
"currency": "BRL",
"amount": 9900,
"first_charge_amount": 0,
"is_default": true
}'Response 201 Created
{
"success": true,
"data": {
"id": "opr_b1c2d3e4f5",
"offer_id": "ofr_a1b2c3d4e5",
"currency": "BRL",
"amount": 9900,
"first_charge_amount": 0,
"is_default": true,
"created_at": "2026-05-19T12:00:00.000Z",
"updated_at": "2026-05-19T12:00:00.000Z"
},
"request_id": "req_8f3c2a1b9d",
"timestamp": "2026-05-19T12:00:00.000Z"
}Send an Idempotency-Key header (or idempotency_key in the body) on create requests. Replaying the same key returns the original result with HTTP 200 instead of creating a duplicate. See Idempotency.
You cannot add a second price in a currency the offer already prices, and you cannot add a second default. Both cases return 409 Conflict with a conflict_error. Update or soft-delete the existing price first.
GET/api/v1/offers/:offerId/prices
Auth: Organization key (with merchant_id) or Merchant key.
Lists the prices on an offer, default first then by currency. Supports filtering and pagination.
Path parameters
| Field | Type | Required | Description |
|---|---|---|---|
offerId | string | Yes | The offer whose prices to list (ofr_). |
Query parameters
| Field | Type | Required | Description |
|---|---|---|---|
currency | string | No | Filter to a single ISO 4217 currency (exactly 3 characters). |
is_default | boolean | No | When true, return only the default price; when false, exclude it. |
page | integer | No | Page number, 1-indexed. Default 1, min 1. |
limit | integer | No | Items per page. Default 20, max 100. |
curl "https://api.tokeflow.com/api/v1/offers/ofr_a1b2c3d4e5/prices?page=1&limit=20¤cy=BRL&is_default=true" \
-H "Authorization: Bearer sk_live_mer_REPLACE_ME"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-05-19T12:00:00.000Z",
"updated_at": "2026-05-19T12:00:00.000Z"
}
],
"meta": {
"pagination": {
"page": 1,
"limit": 20,
"total": 1,
"total_pages": 1,
"has_next": false,
"has_prev": false
}
},
"request_id": "req_3a1d7f9c2b",
"timestamp": "2026-05-19T12:00:00.000Z"
}GET/api/v1/offer-prices/:id
Auth: Organization key (with merchant_id) or Merchant key.
Retrieves a single price by its opr_ ID. The price must belong to an offer owned by the authenticated merchant.
Path parameters
| Field | Type | Required | Description |
|---|---|---|---|
id | string | Yes | The offer price ID (opr_). |
curl https://api.tokeflow.com/api/v1/offer-prices/opr_b1c2d3e4f5 \
-H "Authorization: Bearer sk_live_mer_REPLACE_ME"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-05-19T12:00:00.000Z",
"updated_at": "2026-05-19T12:00:00.000Z"
},
"request_id": "req_5b2e8c0a1f",
"timestamp": "2026-05-19T12:00:00.000Z"
}A missing or out-of-scope price returns 404 Not Found with a not_found_error.
PATCH/api/v1/offer-prices/:id
Auth: Organization key (with merchant_id) or Merchant key.
Partially updates a price. Send only the fields you want to change.
Path parameters
| Field | Type | Required | Description |
|---|---|---|---|
id | string | Yes | The offer price ID (opr_). |
Body parameters (all optional)
| Field | Type | Required | Description |
|---|---|---|---|
currency | string | No | New ISO 4217 code (exactly 3 characters). Must not collide with another live price on the same offer. |
amount | integer | No | New standard amount in minor units (minimum 0). |
first_charge_amount | integer | No | New first-cycle amount in minor units (minimum 0). |
is_default | boolean | No | Promote this price to the offer's default. Fails if another default already exists. |
curl -X PATCH https://api.tokeflow.com/api/v1/offer-prices/opr_b1c2d3e4f5 \
-H "Authorization: Bearer sk_live_mer_REPLACE_ME" \
-H "Content-Type: application/json" \
-d '{ "amount": 12900 }'Response 200 OK
{
"success": true,
"data": {
"id": "opr_b1c2d3e4f5",
"offer_id": "ofr_a1b2c3d4e5",
"currency": "BRL",
"amount": 12900,
"first_charge_amount": 0,
"is_default": true,
"created_at": "2026-05-19T12:00:00.000Z",
"updated_at": "2026-05-19T13:45:00.000Z"
},
"request_id": "req_9d4f1a7e3c",
"timestamp": "2026-05-19T13:45:00.000Z"
}Changing currency into one another live price already uses, or setting is_default: true when the offer already has a default, returns 409 Conflict. To move the default, demote the current default first (or soft-delete it), then promote the new one.
Editing a price changes future charges only. It does not retroactively alter charges that already happened at the connected payment provider.
DELETE/api/v1/offer-prices/:id
Auth: Organization key (with merchant_id) or Merchant key.
Soft-deletes a price. The record is retained for auditability and can be brought back with restore. Soft-deleting frees the price's currency slot — and, if it was the default, the default slot — so a new price can occupy them.
Path parameters
| Field | Type | Required | Description |
|---|---|---|---|
id | string | Yes | The offer price ID (opr_). |
curl -X DELETE https://api.tokeflow.com/api/v1/offer-prices/opr_b1c2d3e4f5 \
-H "Authorization: Bearer sk_live_mer_REPLACE_ME"Response 204 No Content
The response body is empty on success.
POST/api/v1/offer-prices/:id/restore
Auth: Organization key (with merchant_id) or Merchant key.
Restores a soft-deleted price, returning it to active use. Restore is rejected if a live price has since taken the same currency slot, or — for a default price — if the offer already has a default again.
Path parameters
| Field | Type | Required | Description |
|---|---|---|---|
id | string | Yes | The soft-deleted offer price ID (opr_). |
curl -X POST https://api.tokeflow.com/api/v1/offer-prices/opr_b1c2d3e4f5/restore \
-H "Authorization: Bearer sk_live_mer_REPLACE_ME"Response 201 Created
{
"success": true,
"data": {
"id": "opr_b1c2d3e4f5",
"offer_id": "ofr_a1b2c3d4e5",
"currency": "BRL",
"amount": 12900,
"first_charge_amount": 0,
"is_default": true,
"created_at": "2026-05-19T12:00:00.000Z",
"updated_at": "2026-05-19T14:10:00.000Z"
},
"request_id": "req_1c7a3e9f2d",
"timestamp": "2026-05-19T14:10:00.000Z"
}Restoring an already-active price returns 400 Bad Request (validation_error). A currency or default collision with a live price returns 409 Conflict — clear or rename the conflicting price first.
Errors
Offer price endpoints use Tokeflow's standard error envelope. Common cases:
| HTTP | type | When |
|---|---|---|
400 | validation_error | Malformed body, bad currency length, negative amount, or restoring an already-active price. |
401 | authentication_error | Missing or invalid secret key. |
403 | authorization_error | Key lacks the required offers:read / offers:write scope. |
404 | not_found_error | The price or its parent offer does not exist or is out of the key's scope. |
409 | conflict_error | Duplicate currency on the offer, a second default, or a restore that collides with a live price. |
429 | rate_limit_error | Rate limit exceeded — back off exponentially. See Rate limits. |
{
"error": {
"type": "conflict_error",
"code": "OFFER_PRICE_CURRENCY_EXISTS",
"message": "Offer ofr_a1b2c3d4e5 already has a price in currency BRL",
"details": {},
"request_id": "req_2f8b1d4c7a",
"timestamp": "2026-05-19T12:05:00.000Z"
}
}Related
- Offers — the billing configuration prices attach to.
- Products — the sellable items offers belong to.
- Product Families — grouping for products.
- Authentication — keys, scopes, and merchant context.