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_minorand so on. - The value is a string holding an integer:
"120000", never1200.00and never120000as 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
currencyfield holding an ISO 4217 code in capitals, such asUSD,EUR,PKRorINR.
{
"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.
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 decimalsSending 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 mean | Currency | Send |
|---|---|---|
| 450.00 | USD | "45000" |
| 1,200.50 | EUR | "120050" |
| 25,000.00 | PKR | "2500000" |
| 0.99 | GBP | "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_minorandbalance_minorare 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.
Money-related errors
| Code | Status | When |
|---|---|---|
validation_failed | 422 | A money field is a float, a decimal string, negative where that is not allowed, or missing its currency |
currency_mismatch | 422 | The currency does not fit, for example a QR rail on an invoice in another currency |
credit_exceeds_balance | 422 | A credit note is larger than what can still be credited |
schedule_total_mismatch | 422 | Payment schedule instalments do not add up to the document total |
journal_unbalanced | 422 | A 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.