"29.99", "0.0" — period decimal, no thousands grouping), both in responses and in request bodies. Strings preserve decimal precision and avoid the floating-point rounding issues common with JSON numbers — critical when an admin is setting prices and costs. The format is the same regardless of locale; localized formatting is a presentation concern handled by the client (see Canonical format).
Reading
Every monetary field is returned as a string, alongside adisplay_ companion that includes currency formatting:
display_* for rendering and the raw string fields for calculations (parseFloat(price.amount)).
Writing
Send amounts back as strings too, so a value reads and writes in the same type:"amount": 29.99), but strings are recommended: they round-trip with what the API returns and sidestep float precision. A null clears the value.
Canonical format — not localized
Amounts are canonical: a period decimal separator and no thousands grouping ("1234.56"), independent of any locale. The Admin API does not parse locale-specific formats — do not send "1.234,56" or "1,234.56".
If you are taking input from a merchant in a localized format, normalize it to canonical form before sending. The Spree dashboard does this in the browser: a EUR price typed as 1.234,56 becomes 1234.56 on the wire. This matches how other commerce APIs handle money (canonical decimal or minor-unit integers; localization is a presentation concern).
Affected types
This convention applies to all monetary fields across all resources, including back-office-only fields the Store API never exposes:| Resource | Monetary Fields |
|---|---|
| Product / Variant | price, compare_at_amount, cost_price |
| Price | amount, compare_at_amount |
| Order | total, item_total, ship_total, tax_total, adjustment_total, promo_total, included_tax_total, additional_tax_total |
| Line Item | price, total, adjustment_total, promo_total, pre_tax_amount, discounted_amount, compare_at_amount |
| Payment | amount |
| Refund | amount |
| Shipment | cost |
| Gift Card | amount, amount_used, amount_authorized, amount_remaining |
| Store Credit | amount, amount_used, amount_remaining |
Amounts inside preferences
Calculators and some promotion rules carry their amounts inside an untyped preferences object (Record<string, unknown>) rather than as top-level fields. The same canonical-string convention applies to the money-valued keys within that hash — they read back as strings and accept canonical decimal strings on write. The keys that are money:
| Resource | Money keys in preferences |
|---|---|
Calculator flat_rate, per_item, digital_delivery | amount |
Calculator flexi_rate | first_item, additional_item |
Calculator price_sack | minimal_amount, normal_amount, discount_amount |
Calculator tiered_flat_rate | base_amount, and the values of tiers |
Calculator flat_rate (shipping) | amount, minimum_item_total, maximum_item_total |
Promotion Rule item_total | amount_min, amount_max |
percent, flat_percent, base_percent), weights (minimum_weight, maximum_weight), and quantities (max_items, min_quantity) are not money — those stay numbers. Promotion actions (create_adjustment, create_item_adjustments) hold their money inside a nested calculator.preferences, per the calculator rows above.
