Skip to content

Money and quantities

Open .md

YoPrint sends and takes money and quantities as exact values, not floating-point numbers. Different fields use different encodings, listed below. The exception is a Shippo rate request, which takes plain numbers. Before you read or send an amount, check its encoding here or on its reference page.

  • Read dp every time you read a decimal pair. The scale is not fixed: dp can differ between fields and even between records of the same field.
  • Send X-Yoprint-Decimal-Format: exact-v1 on every request. This header ensures amounts that cannot be represented as exact JSON numbers return as strings.
  • Use a decimal type for arithmetic. Never use native binary floating-point numbers for money or quantities.
  • Prefer decimal-pair fields over deprecated thousandths fields. Where an older calc_* field has a decimal-pair replacement, read the replacement instead.
  • Check each field’s reference entry for its encoding. Fields use different encodings across answers and requests.
Encoding Looks like Means Used for
Decimal pair { "value": 1250, "dp": 2 } 12.50: value ÷ 10^dp Newer amounts: line prices, costs and quantities, order and invoice totals
Thousandths 12500 12.50: ÷ 1,000 Older amounts: payments, customer balances, product prices, and the deprecated calc_* totals
Percentage × 1000 8250 8.25%: ÷ 1,000 Tax rates and deposit percentages
Ten-thousandths of a percent 82500 8.25%: ÷ 10,000 The automatic sales tax lookup’s rate
Decimal string "24.00000000", "12.40" The number as written Stock levels, Shippo rates and weights, some report rows
Plain number 12.5 The number as written Shippo rate requests only
Formatted string "$125.00" Text for people The payment webhook’s amount: don’t parse it
Every field, by encoding
  • Decimal pair (requests and answers): line item prices, costs, and quantities. Order totals (order_item_cart_total, invoiced_total, order_balance_due, deposit_amount_due, shipment_cost_total, margin, markup). Invoice totals (total, cart_item_subtotal, tax_subtotal, …) and line items.
  • Thousandths (requests and answers), older fields:
    • Order amounts: paid_amount and calc_deposit_total.
    • Customer balances: unpaid_amount and wallet_fund.
    • Product prices: default_price.
    • Payments and refunds: amount, sent and answered.
    • v1 filter amounts, and package label charges.
    • The deprecated calc_* totals on orders and invoices.
  • Percentage × 1000 (requests and answers): tax preset rates, payment term deposit_percent, a shipping method’s price_percent, and an order’s deposit_total in answers (sent as a pair with dp 3).
  • Ten-thousandths of a percent (answers): the automatic sales tax lookup’s total (the combined rate).
  • Decimal string, stock (answers): stock levels (stock_on_hand, stock_incoming, qty_need_ordering), with 8 decimal places, or "0".
  • Decimal string, money and weights (answers): Shippo rates and package weights (amount, weight). Some report rows (purchase order report calc_*, tax report amounts).
  • Plain number (requests): Shippo rate requests: parcel dimensions, parcel weights, and insurance amount (Shippo’s own format, passed through).
  • Formatted string (answers): the payment webhook’s amount, in the team’s currency. Read the order or invoice for exact amounts.

YoPrint is moving amounts to decimal pairs:

  1. BeforeAmounts in thousandths, such as an order’s calc_total
  2. NowDecimal pairs added. The old calc_* copies still come back, marked deprecated, so existing integrations keep working
  3. LaterThe deprecated copies are removed. Thousandths fields without a pair yet may change too
Each field's reference entry says which encoding it uses today.

See Deprecated fields for each old field and the one to read instead.

  1. value1250: the digits, with no decimal point
  2. dp2: how many of those digits come after the point
  3. The amount1250 ÷ 10² = 12.50
Read both parts every time: the same field can come back with a different dp.

A decimal pair consists of an integer value and a scale dp. The amount is value ÷ 10^dp:

  • { "value": 1250, "dp": 2 } is 1250 ÷ 10² = 12.50.
  • { "value": 125, "dp": 1 } is 125 ÷ 10¹ = 12.5.
  • { "value": 3, "dp": 0 } is 3 (a quantity of three).

When you read a pair, never assume dp. Answers keep the scale at which the amount is stored in YoPrint. The same field can return with different scales across different records, and an unset amount can return as { "value": 0, "dp": 0 }. Always read dp directly from the object.

Kind of field Typical dp Example
Order and invoice totals 4 { "value": 1250000, "dp": 4 } (125.00)
Line item unit prices and costs 6 { "value": 12500000, "dp": 6 } (12.50)
Line item quantities 0 { "value": 12, "dp": 0 } (12 items)
Unset amounts 0 { "value": 0, "dp": 0 } (0.00)
  1. The field12500
  2. 1,000The same for every thousandths field
  3. The amount12.50
Older fields count thousandths of the currency unit.

A thousandths field represents an amount multiplied by 1000. The amount is value ÷ 1000:

  • 12500 is 12500 ÷ 1000 = 12.50.
  • 125000 is 125000 ÷ 1000 = 125.00.

In answers, the value is usually a whole number. When an amount is finer than a thousandth, it returns as a fractional number: 1234.5 is 1234.5 ÷ 1000 = 1.2345. When you send X-Yoprint-Decimal-Format: exact-v1, any fractional thousandths value (or any value too large for a JSON number) returns as an exact decimal string of thousandths, such as "1234.5".

A percentage × 1000 represents a percentage multiplied by 1000: 8250 is 8250 ÷ 1000 = 8.25%. With X-Yoprint-Decimal-Format: exact-v1, any value that is not a whole number returns as an exact string.

Stock levels return as strings with 8 decimal places (such as "24.00000000"), and Shippo rates return as decimal strings (such as "12.40"). Always parse them into your language’s decimal type.

Payment webhooks include an amount field formatted as a currency string (such as "$125.00"). This string is intended for display to people, not for calculations. Do not parse it: currency symbols, digit separators, and decimal conventions vary by team currency. To get exact amounts, fetch the order or invoice from the API.

Most mistakes with money happen when sending it. Find the field you send in this table, and send it in that unit. Each example is 12.50 (or 8.25% for a rate).

You send Fields Unit Example
Prices, costs and quantities on order, invoice and shipment lines pricing.…total, quantity.total Decimal pair { "value": 1250, "dp": 2 }
A payment, a refund, or a card refund through the gateway amount Thousandths 12500
Customer credit: add or take out, pay an order from it, move payments to it amount Thousandths 12500
A payment link’s allocations total A pair where value counts thousandths, whatever dp says { "value": 12500, "dp": 3 }
Product and variant prices default_price, and price in price_data Thousandths 12500
Fee preset prices, a shipping method’s minimum price price, min_price Thousandths 12500
Shipping box sizes length, width, height Thousandths of the unit 12500 is 12.5
Tax preset rates, a payment term’s deposit, a shipping method’s markup the rate fields, deposit_percent, price_percent Percentage × 1000 8250
An order’s deposit percentage deposit_total Percentage × 1000, in a pair with dp exactly 3 { "value": 8250, "dp": 3 }
Shippo rate requests parcel sizes and weights, insurance amount Plain number, as Shippo takes it 12.5

When a field isn’t in this table, its reference page says its unit.

When sending a decimal pair in a request body:

  • value must be an integer, or a string of digits with an optional leading -. No decimal point and no leading zeros.
  • dp must be an integer from 0 to 30.
  • A plain number such as 12.5, or a pair with a fractional number such as { "value": 12.5, "dp": 1 }, is refused with HTTP 422.
  • YoPrint limits the maximum scale and value by field type:
Kind of field Most decimal places Largest value
Quantities 8 999,999,999,999.99999999
Unit prices, costs, and rates 6 999,999,999,999,999.999999
Calculated amounts (discounts, totals) 4 999,999,999,999,999.9999

Sending more decimal places than allowed is refused with HTTP 422, unless the trailing digits are zeros.

Send a whole number of thousandths, with no decimal point: 25000 is 25.00. What happens to a number with decimals depends on the field:

  • A payment or refund amount, and every customer credit amount (adding or taking out credit, paying an order from it, moving payments to it): refused with 422 validation_failed, and nothing is recorded. error.fields.amount says “The amount must be a whole number of thousandths of the currency unit, such as 12500 for 12.50.”
  • Product and variant prices: the request fails with HTTP 500. Fix the number before you send it again.

The sign of a payment or refund amount is ignored: -25000 also records 25.00.

Send this header on every request:

X-Yoprint-Decimal-Format: exact-v1

With this header:

  • A decimal pair whose value is too large for a JSON number to hold exactly (beyond 9,007,199,254,740,991, which is 2^53 − 1) returns with value as a string of digits.
  • A thousandths field that is not a whole number returns as an exact decimal string of thousandths, such as "1234.5".
  • A percentage × 1000 that is not a whole number returns as an exact decimal string.

Without it, a save of an order, an invoice, a shipment or their lines is refused with 422 amount_out_of_range when its answer would carry an amount the older number format can’t hold exactly. Nothing is saved: send the header and try again.

Both v1 and v2 accept this header, and YoPrint echoes X-Yoprint-Decimal-Format: exact-v1 in the response when it applied it. Always parse value and thousandths fields as either a number or a string. The client helpers on Calling the API send this header on every request.

What each order total counts, and how the balance is worked out, is on How the balance works. Its fields are decimal pairs, except paid_amount, which is in thousandths.

Keep amounts as integers or strings until you display them. Do not convert to a binary floating-point number and back.

// "4017.86" from { value: 40178600, dp: 4 }
function pairToString({ value, dp }) {
const digits = String(value).replace('-', '').padStart(dp + 1, '0');
const sign = String(value).startsWith('-') ? '-' : '';
const whole = digits.slice(0, digits.length - dp);
const fraction = digits.slice(digits.length - dp).replace(/0+$/, '');
return sign + whole + (fraction ? '.' + fraction : '');
}
// "125" from 125000, and "1.2345" from 1234.5 or "1234.5" (thousandths)
function thousandthsToString(n) {
const [whole, fraction = ''] = String(n).split('.');
return pairToString({ value: whole + fraction, dp: 3 + fraction.length });
}

Use your language’s decimal type for arithmetic: decimal.Decimal in Python, BCMath or brick/math in PHP, or a decimal library in JavaScript.

Amounts are in the team’s currency: an order takes the team’s default currency when it’s made. No numeric encoding carries the currency. Only the formatted display strings in webhooks include a currency symbol.