POST
Set the tax-inclusive display convention for a currency or region

Authorizations

Authorization
string
header
required

A secret API key. Publishable keys cannot reach this API. A key may carry an expiry, and an expired key is refused exactly like an unknown one, with a 401 that names no reason; check the key's expires_at in the dashboard rather than inferring it from a response. When a merchant rolls a key's secret they choose a grace window of up to 3 days, and for its duration BOTH the new secret and the one it replaced authenticate, so an integration moves over on its own deploy schedule instead of at the instant the button is pressed. Move before the window closes: after it, the old secret is refused. Nothing else about this contract moves with a roll. The key keeps its id and its scopes, so the only thing an integration updates is the credential itself.

Headers

Idempotency-Key
string
required

A unique key per logical write. Replaying a request with the same key returns the first response byte for byte instead of applying the write twice.

Body

application/json

WHAT A SHOPPER IS SHOWN, not what they are charged. This row moves no money and changes no amount: it decides whether a price is RENDERED with tax already in it, for one currency or one region. Getting it wrong is a store showing every VAT-market shopper a price below the total checkout collects. AN UPSERT KEYED ON (attribute, value), which is why it is a POST to the collection and answers 200 rather than 201. The table carries UNIQUE (store_id, attribute, value) and the id is server-minted, so before a preference exists there is no path that could name it, and after it exists the natural key is still the only handle a client holds. is_tax_inclusive is an ABSOLUTE value, so a replay sets the same flag and the row keeps its id and its original created_by: a retry outside the idempotency window behaves exactly like one inside it, which is what makes this route safe for a migration tool to re-run. A REGION PREFERENCE BEATS A CURRENCY ONE, and this is the trap an integrator hits. Setting attribute currency_code with value usd to true does nothing for a shopper whose region already carries a preference of its own, and the 200 says nothing about it. Read GET /api/v1/price-preferences and look at BOTH dimensions before concluding a write took effect. Neither dimension matching means tax-exclusive. IT TAKES EFFECT IMMEDIATELY. The storefront resolves this at READ time and never snapshots it, so every price rendered after this call uses the new convention and carts already open re-render. Nothing is emitted: this family registers no webhook topics, so a client caching the preference must poll. SETTING is_tax_inclusive FALSE IS NOT THE SAME AS DELETING THE ROW, and the difference decides which one you want. Resolution is the region row, then the currency row, then false, so false written HERE is PINNED and shadows the currency row underneath it, while DELETE /api/v1/price-preferences/{pricePreferenceId} removes the row and lets that currency row start winning. There is no value of is_tax_inclusive meaning "defer to the dimension below me". This route is the reversible one: it records the merchant's choice rather than erasing it, and it needs write_pricing alone where the delete also needs write_pricing:delete. id, created_at, updated_at and created_by are refused BY NAME rather than ignored: the server mints the id and owns the timestamps, and created_by is the merchant staff audit trail for SOC2, which an api key is not a member of. tax_inclusive and taxInclusive are refused by name because they are the two spellings a client reaches for first and a silent miss would leave the flag at false. currency_code and region_id are refused as KEYS: the code or the id goes in value, with attribute naming which dimension it is, so one route writes both.

attribute
enum<string>
required

Required. The dimension the preference is keyed on. The column carries a CHECK constraint admitting exactly these two, so anything else is a 400 and a client may switch on it exhaustively today; a third value could only ever arrive additively.

Available options:
currency_code,
region_id
value
string
required

Required, trimmed, and matched EXACTLY at resolution time. With attribute currency_code it must be three ASCII letters and is stored lowercase, so "USD" comes back as "usd"; with attribute region_id it is an opaque region id whose case is preserved, because folding it would break a lookup rather than fix one. The cap is 128 BYTES rather than characters, so a multi-byte value reaches it sooner than its length suggests. An empty value is a 400. A value that matches nothing is NOT an error, it is silently inert: the store keeps rendering tax-exclusive prices while the merchant's dashboard shows a preference that looks set.

Maximum string length: 128
is_tax_inclusive
boolean
required

Required, and the whole payload of this resource. True means prices for this currency or region are DISPLAYED with the tax already in them. It is not defaulted, because omitting it would decode to false and show every shopper in that currency or region a price below the one checkout collects, with a 200 and no event to tell anyone. The admin body defaults it; a published contract cannot.

Response

Success

data
object