Quotes
A quote is a short-lived, binding price — every order is priced by one, so the amount is fixed before you commit.
A quote locks a binding price for one purchase. Every order is priced by a quote, so the amount your wallet is debited is fixed before anything is reserved. Create one here when a buyer should see the exact total before committing; otherwise send the item straight to order creation, which quotes it in the same call and can refuse a price above your ceiling. Creating a quote requires the quotes.create scope.
Create a quote
POST /api/v1/quotes
{
"productId": "0198d72d-99d6-75a6-9f12-971050ba7a5f",
"variantId": "0198d72d-a15b-7cbf-ab69-b95410e98636",
"value": null,
"quantity": 1,
"currency": "USD"
}| Field | Rules |
|---|---|
productId | Required. A product from your catalog. |
variantId | Use an available variant's id for a variant purchase; do not also supply value. |
value | For a purchase without variantId, supply a positive denomination in the product's currency. It must satisfy the available range, increment, and currency precision. If omitted, the product's default faceValue is used when present. |
quantity | 1–20. |
currency | Must equal the product's sellCurrency, the workspace offer's settlement currency. Do not copy denomination currency unless it is the same. |
For example, a product with currency: "USD" and sellCurrency: "EUR" can be quoted with value: 50 and currency: "EUR": the product represents 50 USD and the quote returns the amount payable in EUR. The API determines that total; do not convert denominations into an estimated checkout price yourself. Use decimal arithmetic for monetary values.
A valid request returns 200 OK:
{
"id": "0198d72d-a6ce-77c6-9586-fc36708731d0",
"productId": "0198d72d-99d6-75a6-9f12-971050ba7a5f",
"variantId": "0198d72d-a15b-7cbf-ab69-b95410e98636",
"quantity": 1,
"total": 52.50,
"currency": "USD",
"expiresAtUtc": "2026-09-05T12:05:00+00:00",
"unitPrice": 52.50,
"productName": "Everyday Digital Gift Card",
"variantLabel": "$50",
"faceValue": 50.00,
"faceCurrency": "USD",
"requestedValue": null,
"status": "active"
}| Field | Meaning |
|---|---|
total | The settlement amount the order will reserve and debit, in currency. |
unitPrice | The settlement price of one unit; total is unitPrice × quantity. |
expiresAtUtc | After this instant the quote can no longer be ordered. |
productName, variantLabel | The catalog names as they read now, so a receipt can be rendered from the quote alone. A quote stores no names of its own, so a rename in the catalog changes what a quote read back says. variantLabel is null for a custom amount. |
faceValue, faceCurrency | The denomination the buyer receives (see below); both null when the item is not money. |
requestedValue | The custom amount that was quoted, in the product currency; null for a variant purchase. |
status | active — an order can be placed against it until expiresAtUtc; used — an order was placed against it; expired — no order can be placed against it any more. A new quote is always active. |
Read a quote back
GET /api/v1/quotes/{quoteId} (scope: quotes.create or orders.create)
Returns the quote as it was made — the same total, unit price and denomination; nothing is priced again — with its current status. The money is frozen, the names are not: productName and variantLabel are read from the catalog at the moment you ask, because a quote records no names of its own. (An order is the opposite: its lines keep the names recorded when it was placed — see Orders.) A key that makes quotes and a key that orders against them can both read the workspace's quotes, so the service that places the order can confirm what it is about to buy. Another workspace's quote, or an id that does not exist, is 404 not_found. Use status rather than the clock to decide whether a quote can still be ordered: an active quote past its expiry reads expired.
Denomination and settlement
A quote names two amounts, and they are different things:
total(and the per-unitunitPrice) is the settlement amount — what your wallet pays, incurrency, the offer'ssellCurrency.faceValue/faceCurrencyis the denomination — what the buyer receives: a 50 EUR card, a 1000 JPY top-up. It is a fact about the product, frozen on the quote exactly as it was offered, and it is never an input to the price. For a variant purchase it is the variant's own denomination (a few variants are denominated in a currency other than their product's); for a custom amount it is thevalueyou sent, in the product's currency, andrequestedValueechoes it. Both arenullwhen the variant is not money — a data plan has no denomination.
Show the buyer both sides — "50 EUR gift card, 52.50 USD" — and never derive one from the other.
The rules that keep pricing honest
totalis final. It is the exact amount order creation will reserve and, on fulfillment, debit from your wallet. No fees are added later.- Quotes expire.
expiresAtUtcis typically a few minutes out. An order against an expired quote is rejected — quote at the moment of purchase, not at page-render time. - One quote, one order. Reusing a quote for a second order is rejected; idempotent retries of the same order are handled by the
Idempotency-Key, not by re-quoting. - Quotes are workspace-bound. A quote created by one workspace's key can never be read or ordered by another.
Displaying prices without quoting
The catalog exposes numeric faceValue fields for displaying denominations. A denomination is the amount the product represents, not the amount your wallet will pay. Variant price, product priceFrom/priceTo and customAmount.indicativeRate/indicativeFlat are indicative: computed by the same pricing path a quote runs, on the inputs the catalog read saw, but not locked — rates and availability move between a listing and a checkout. They are nullable; do not display them as zero or substitute faceValue as a sell price. A custom amount costs approximately indicativeRate per face unit plus indicativeFlat, both deliberately unrounded, so working the figure out is an estimate the quote will refine: a quote prices the amount on costs rounded up to whole minor units, so on unchanged inputs it can come out slightly above the estimate, never below it. indicativeFlat exists because part of a price does not grow with the amount, and a single multiplier would be right at one amount only. Request a quote for the binding total and use its returned currency and expiresAtUtc.
Common rejections
| Status | code | Why |
|---|---|---|
| 400 | invalid_request | Quantity outside 1–20, missing/contradictory selection, off-grid value, an amount not representable in the denomination currency, a malformed currency, or a body member the request does not have. |
| 403 | api_access_inactive | The workspace may not quote right now (reason says why); see Authentication. |
| 403 | entitlement_required | API purchasing is not enabled for the workspace — normally answered earlier as api_access_inactive. |
| 404 | not_found | The product is not in your catalog. |
| 422 | currency_not_supported | The requested settlement currency does not match the offer; refresh the product and use its sellCurrency. |
| 422 | product_unavailable | The product or variant became unavailable between display and quote. |
| 422 | pricing_unavailable | The item cannot be priced right now; try again shortly. |
Handle quote rejection as a normal user-facing event ("this item just became unavailable") rather than an exceptional error — availability is re-verified at every step by design.
Updated 18 days ago