Using the APIMoney and currencies

Using the API

Money and currencies

Amounts are strings of integer minor units with an ISO 4217 currency. How to send prices, read totals, and work with several currencies.

Every amount in the API is a string of integer minor units together with a currency code. There are no floating-point amounts anywhere, in requests or responses. This keeps totals exact to the last cent, in every language, and means your invoice adds up the same way in your code, in our app and on the PDF.

The rule

  • Money fields end in _minor: unit_price_minor, total_minor, amount_minor, balance_minor and so on.
  • The value is a string holding an integer: "120000", never 1200.00 and never 120000 as a JSON number.
  • The value counts the currency's minor unit. For USD that is cents, so "120000" is 1,200.00 USD.
  • Every money field sits next to a currency field holding an ISO 4217 code in capitals, such as USD, EUR, PKR or INR.
Money on a document
{
  "currency": "USD",
  "subtotal_minor": "120000",
  "tax_minor": "0",
  "total_minor": "120000",
  "amount_paid_minor": "50000",
  "balance_minor": "70000"
}

That document totals 1,200.00 USD, 500.00 USD has been paid, and 700.00 USD is still owed.

Why strings

JSON numbers are often parsed as double-precision floats, which cannot represent every large integer exactly and tempt code into decimal maths. Strings arrive in your program exactly as we sent them. Convert them to an integer type (BigInt in JavaScript, int in Python, long or a decimal type elsewhere) before doing arithmetic.

JavaScript
const balance = BigInt(document.balance_minor); // 70000n
const display = new Intl.NumberFormat("en", { style: "currency", currency: document.currency })
  .format(Number(balance) / 100); // "$700.00" for a currency with 2 decimals

Sending a float such as "unit_price_minor": 450.5 or a decimal string such as "450.50" is rejected with validation_failed (422). Multiply by the currency's minor unit first, then send the integer as a string.

Converting to and from minor units

The number of minor units in one major unit depends on the currency, following ISO 4217. For the many currencies with two decimals, multiply by 100:

You meanCurrencySend
450.00USD"45000"
1,200.50EUR"120050"
25,000.00PKR"2500000"
0.99GBP"99"

When you read amounts back, divide by the same factor only for display. Keep stored values in minor units.

The one place where the API accepts decimal prices is a CSV file: Import items from CSV reads unit_price as a decimal such as 450.00 and converts it with the currency's decimals. Everywhere in JSON, use minor units.

Quantities and rates are not money

Line quantities, discount values and tax rates are not money, so they are not minor units. A line quantity can be fractional, for example "1.5" hours. Send it as a decimal string as shown on the endpoint pages. The server multiplies quantity by the unit price and rounds the result into minor units for you.

Who does the maths

All totals are computed on our side by one shared, tested module: line subtotals, discounts, taxes, document totals, amounts paid, amounts credited and balances. You send the inputs (lines, quantities, unit prices, tax rates, discounts); the response gives you the results.

  • Do not send totals. Fields such as total_minor and balance_minor are read-only.
  • Read totals from the response. They match the PDF, the pay page and the reports exactly.
  • Balance is total_minor - amount_paid_minor - amount_credited_minor.

Currencies on documents

Each document has one currency. A business can issue documents in any currency; each line uses the document's currency.

Exchange rates

When a document's currency differs from the business's base currency, the document keeps an exchange rate to the base currency. The rate is frozen on the document, so a report run next year shows the same base-currency value as today. See Currencies and exchange rates for how the rate is chosen and when you can set it yourself.

Reports in several currencies

Reports never add different currencies together silently. Multi-currency report amounts are returned per currency, plus a total in the business currency calculated with each document's frozen rate. See Reports.

Payments must match

A payment is recorded in the document's currency. Payment methods can also depend on the currency: the local QR rails (UPI for INR; Raast, JazzCash and Easypaisa for PKR) only appear on invoices in their own currency. Asking for one of them on an invoice in another currency returns currency_mismatch (422). See Local QR payments.

CodeStatusWhen
validation_failed422A money field is a float, a decimal string, negative where that is not allowed, or missing its currency
currency_mismatch422The currency does not fit, for example a QR rail on an invoice in another currency
credit_exceeds_balance422A credit note is larger than what can still be credited
schedule_total_mismatch422Payment schedule instalments do not add up to the document total
journal_unbalanced422A ledger journal entry's debits do not equal its credits

Details for each are on Errors.

In webhooks

Webhook events use exactly the same format: "total_minor": "125000" with "currency": "PKR" is 1,250.00 PKR. Parse them the same way as API responses.