Money
How the API represents prices and totals as integer minor units, how to display them, and how tax-inclusive and tax-exclusive prices are labelled.
Every price, total and discount in the API is a money object with an integer amount in minor units. There are no floats and no decimal strings to parse. This page explains the shape, how to display it and how to send amounts back.
The money object
{ "amount": 8900, "currency": "GBP", "formatted": "£89.00" }| Field | Type | Meaning |
|---|---|---|
amount | integer | The value in the currency's minor unit: pence for GBP, cents for EUR and USD. 8900 is £89.00. |
currency | string | The ISO 4217 currency code, such as GBP. |
formatted | string | The amount ready to show, formatted for the UK (£89.00). |
The same shape is used everywhere: product prices, cart lines, shipping options, discounts, tax, order totals and search results. A money field that has no value is null, never 0 and never a missing object. For example, a variant has no price when the store has not set one in its currency, and cart totals are null until the cart has been calculated.
Merchant CRM money custom fields use the same idea without formatted: { "amount": 250000, "currency": "GBP" }.
Never use floats
Floating-point numbers cannot hold most decimal fractions exactly, so 0.1 + 0.2 is not 0.3. Keep amounts as integers from the moment they arrive until you display them.
- Show
formatted. It is correct for the currency and already rounded. - Do not add up totals yourself. Promotions, shipping discounts and tax all change the numbers. Read
sub_total,discount_total,tax_totalandtotalfrom the cart or order. - Compare and sort on
amount, never onformatted. - Send integers back. Query parameters that take money, such as
filter[price_min]andfilter[price_max], take integer minor units. Send2500for £25.00, never25or25.00.
Display amounts in another locale
If your shop shows prices in a format other than the UK one, format amount yourself. Divide by the currency's number of minor units, which Intl.NumberFormat already knows:
type Money = { amount: number; currency: string; formatted: string }
export function formatMoney(money: Money, locale = 'en-GB'): string {
const formatter = new Intl.NumberFormat(locale, { style: 'currency', currency: money.currency })
const digits = formatter.resolvedOptions().maximumFractionDigits ?? 2
return formatter.format(money.amount / 10 ** digits)
}
formatMoney({ amount: 8900, currency: 'GBP', formatted: '£89.00' }, 'de-DE') // "89,00 £"Division here is only for display. Never feed the result back into arithmetic or into a request.
Currency
Each store trades in its own currency. You can read it from:
currencyonGET /v1/checkout/config;currencyon every money object;meta.currencyon search responses.
Do not assume GBP. Read the currency from the response and pass it to your formatter.
Percentages
Percentages that describe money are integers in basis points, so they never need floats either. percent_bp: 1250 is 12.5%. You will see them on a variant's pricing.saving and in a bulk-price table's rows.
Tax rates in a tax breakdown are the exception: percentage is a string, such as "20", meant for display.
Tax-inclusive and tax-exclusive prices
Stores set, per tax zone, whether prices are shown including or excluding tax. A cart tells you which applies through tax_zone:
{
"tax_zone": {
"id": 1,
"name": "UK",
"price_display": "tax_inclusive",
"prices_include_tax": true
}
}| Field | Values |
|---|---|
price_display | tax_inclusive ("Including VAT") or tax_exclusive ("Excluding VAT") |
prices_include_tax | The same, as a boolean |
The zone is the shipping address's, or the store's default zone before an address is set. tax_zone is null when the store has no tax zone.
This setting is for labelling. The amounts are already correct either way: total is always what the shopper pays, tax_total is the tax within it, and tax_breakdown lists each rate:
[
{
"identifier": "vat-standard",
"description": "VAT",
"percentage": "20",
"price": { "amount": 2050, "currency": "GBP", "formatted": "£20.50" }
}
]Use price_display to choose the label next to prices ("inc. VAT" or "ex. VAT"), and show tax_total as its own line when prices exclude tax.