Skip to Content
CheckoutAPI ReferenceSubscriptionsQuote the next charge for a subscription

Quote the next charge for a subscription

POST/subscriptions/{subscriptionId}/charge-quote

Quote the next charge for a subscription: the token amount to sign, plus the locked rate and the price_lock_code to submit with the charge. An optional amount in fiat minor units quotes an ad-hoc charge; omitted, the stored plan price is quoted. Every quote is checked against the subscription's cap_amount and the remaining per-window budget at quote time. Legacy token-denominated plans return the fixed amount with no lock.

Headers

HeaderDescriptionRequired
AuthorizationBearer token with your secret API keyyes

Path Parameters

NameTypeRequiredDescription
subscriptionIdstringyes

Request Body

FieldTypeRequiredDescription
amountstring—Optional ad-hoc charge amount in minor units of the subscription's price_currency. Omitted, the stored plan price is quoted. Bounded by the subscription's cap_amount and the remaining per-window budget.

Example Request

Request

const response = await fetch('https://checkout-api.exodus.com/subscriptions/<subscriptionId>/charge-quote', {
  method: 'POST',
  headers: {
    Authorization: 'Bearer sk_live_xxxxxxxxxxxxxxxx',
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    "amount": "3490000"
  }),
});

Responses

StatusDescription
200The charge quote, including any locked rate.
400Validation error
401Missing or invalid authentication
403API key lacks the required scope
404Resource not found

Response body

FieldTypeRequiredDescription
objectenum: subscription_charge_quoteyes
subscription_idstringyesThe on-chain subscription id the quote is for.
amountstringyesThe amount to sign and submit to the charge, in the settlement token's smallest units. It is the fiat amount converted at rate and rounded to a whole smallest unit. That fiat amount is the ad-hoc amount from the request body when given, else the plan price.
price_lock_codestring | nullyesCode that binds this quote to the subscription, its token and chain, and amount. Pass it to the charge before expires_at. One successful charge consumes the code; a cleanly rejected charge releases it, so you can retry with it until expires_at. A charge whose on-chain outcome is undeterminable holds the code instead; re-quote rather than retry. Minted by the settlement provider on a fiat-settled business, or as a refq_-prefixed reference quote on a crypto-settled one. Legacy token-denominated plans, created before fiat-only creation shipped, return null here and in rate and expires_at; their amount is the plan price unchanged.
ratestring | nullyesConversion rate the quote applied, in whole units of price_currency per one whole token ("696.27" is 696.27 ARS per USDC).
expires_atstring | nullyesISO 8601 timestamp after which the quote can no longer be charged. Quote again past it.

Example Response

{
  "object": "subscription_charge_quote",
  "subscription_id": "0x9f3a4b5c6d7e8f9a0b1c2d3e4f5a6b7c8d9e0f1a2b3c4d5e6f7a8b9c0d1e2f3a",
  "amount": "9990000",
  "price_lock_code": "refq_3f9a1c7d2e5b8064",
  "rate": "696.27",
  "expires_at": "2026-09-16T12:04:18Z"
}

Error Response

Error (e.g. 400)

{
  "error": {
    "type": "validation_error",
    "message": "amount must be a positive number",
    "param": "amount"
  }
}

Quote Semantics

No signature — this call moves no funds. The body is optional: fiat-priced plans may pass amount, in minor units of the plan’s price_currency, to quote an ad-hoc charge instead of the stored price. Omit it to quote the plan price. Token-priced plans take no body.

  • Token-priced plans (price_currency: null) echo the plan’s price as amount, with price_lock_code, rate, and expires_at all null. You can skip the quote entirely and sign price directly.
  • Fiat-priced plans on a fiat-settled business convert the fiat price to a settlement-token amount at the live rate via the settlement provider and lock that rate: pass the returned amount and price_lock_code to POST /subscriptions/:id/charge before expires_at. The provider commits to converting at that rate through ramp-off.
  • Fiat-priced plans on a crypto-settled business (currently ARS-priced plans only) convert the fiat price to a settlement-token amount via an Exodus-minted reference quote instead: same response shape, but price_lock_code carries a refq_-prefixed code with a 120-second TTL. A reference quote doesn’t commit anyone to a conversion, it only proves the charged amount matches what the subscriber was shown. Funds settle in the token the subscriber paid with, nothing converts, and no fiat settlement configuration is needed.

The subscription id is the on-chain bytes32 hex id — obtain it from GET /merchants/:merchantId/subscriptions, the subscription.created webhook, or the subscription_checkout.completed webhook.

On fiat-priced plans, the quote binds the price_lock_code to the exact amount it returned, to this subscription, and to the subscription’s token and chain, whether it’s a provider-minted price lock or an Exodus-minted reference quote. Each code is redeemed once. POST /subscriptions/:id/charge rejects a mismatch with a quote code; re-quote and re-sign after any of them. Token-priced plans (price_currency: null) are not quote-bound: a price_lock_code sent with such a charge is ignored, and none of these codes can fire.

Variable Charges

A fiat-priced plan doesn’t have to charge price every cycle. Pass the amount you want to charge this cycle as amount in the quote body, then sign and submit the token amount the quote returns. Each quote is bound to the amount it converted, so a new amount always means a new quote.

REQUEST
{ "amount": "349000" }
RESPONSE
{
  "object": "subscription_charge_quote",
  "subscription_id": "0x9f3a4b5c6d7e8f9a0b1c2d3e4f5a6b7c8d9e0f1a2b3c4d5e6f7a8b9c0d1e2f3a",
  "amount": "2406897",
  "price_lock_code": "refq_9f3a2b7c4d1e8a05b6c7d2f1",
  "rate": "1450",
  "expires_at": "2026-09-10T15:04:12Z"
}
variable-charge.js
import { CheckoutSigner } from '@exodus/checkout-signer';
 
const headers = { Authorization: `Bearer ${process.env.API_KEY}` };
const signer = new CheckoutSigner();
 
// The subscription's on-chain id, from the subscription_checkout.completed webhook
const subscriptionId = '0x9f3a2b...';
 
const sub = await fetch(`https://checkout-api.exodus.com/subscriptions/${subscriptionId}`, {
  headers,
}).then((r) => r.json());
 
// Quote this cycle's amount in fiat minor units — ARS 3,490.00 here
const quote = await fetch(`https://checkout-api.exodus.com/subscriptions/${sub.id}/charge-quote`, {
  method: 'POST',
  headers: { ...headers, 'Content-Type': 'application/json' },
  body: JSON.stringify({ amount: '349000' }),
}).then((r) => r.json());
 
// Sign the quoted token amount, not the fiat amount
const { signature } = signer.signCharge(sub, { amount: BigInt(quote.amount) });
 
// Submit the same token amount with the quote code before expires_at
await fetch(`https://checkout-api.exodus.com/subscriptions/${sub.id}/charge`, {
  method: 'POST',
  headers: { ...headers, 'Content-Type': 'application/json', 'X-Signature': signature },
  body: JSON.stringify({ amount: quote.amount, price_lock_code: quote.price_lock_code }),
});

Every quote is checked against the subscriber’s ceilings before any funds move, ad-hoc, stored-price and token-priced alike. The quote rejects with amount_exceeds_cap when the token amount it would return is above cap_amount, so a stored-price quote whose conversion drifts over the cap is rejected too. It rejects with budget_exceeded when that amount would push spent_this_period past budget, or when the window is already fully spent. The charge re-checks both, since spend can accrue between quote and charge. Both ceilings are fixed by the subscriber at subscribe time and the merchant can never raise them, so a plan that expects to reprice needs cap and budget headroom declared on the subscription checkout up front.

Quote errors on charge

The quote binding rejections (quote_not_found, quote_expired, quote_amount_mismatch, quote_asset_mismatch, quote_already_used) and the 422 for a fiat-priced charge sent without a price_lock_code are returned by POST /subscriptions/:id/charge, and that page’s Responses table documents each with its status and meaning. This page keeps only the retry guidance the reference table cannot carry.

A charge that fails after the quote was accepted (a typed simulation failure or an on-chain revert) usually releases the quote, so a retry can resubmit the same amount, price_lock_code, and signature until expires_at. If the retry is rejected with a quote error, re-quote and re-sign.

Errors on this endpoint

StatusTypeDescription
400invalid_requestFiat-priced plan on a fiat-settled business without a fiat settlement configuration (“Charge quote is only available for fiat settlements”).
400validation_erroramount is present but isn’t a positive integer string with no leading zeros, or the body carries an unrecognized key; a misspelled amount fails loudly rather than silently quoting the stored price.
422invalid_requestprice_currency_not_quotable — fiat-priced plan on a crypto-settled business, but price_currency isn’t a quotable currency (currently only ARS is).
422invalid_requestamount_exceeds_cap — the token amount the quote would return, converted from an ad-hoc amount or from the stored price, exceeds the subscription’s cap_amount. error.data carries subscription_id and cap_amount.
422invalid_requestbudget_exceeded — the quoted amount would push spent_this_period past the per-window budget, or the window is already fully spent. error.data carries subscription_id, spent_this_period and budget.
422invalid_requestmissing_budget_state — the subscription has no mirrored budget state (started_at or budget unset), so the ceilings can’t be evaluated. error.data carries subscription_id.
422invalid_requestamount_override_not_supported — amount was sent for a token-priced plan, which has no fiat price to override. Sign the token amount directly instead.
404not_foundSubscription ID does not exist or belongs to another merchant.
500invalid_requestpricing_unconfigured — reference-quote path, the Exodus pricing server isn’t configured. Merchant-side retry won’t help.
502invalid_requestFiat-settled: the settlement provider failed to create the price lock. Crypto-settled: rate_unavailable, the Exodus pricing server didn’t return a usable rate. Retry either case.

Token-priced plans never fail on settlement configuration — the quote is a passthrough of price.

Start building

XO

Request Demo

Schedule a call with our team

Select a product
Arrow right

Start building
Grateful

Contact Us

We're here to help