Reading sales data
How to interpret purchases and line items the way Karma does, and how to export a day's sales reliably.
Sales come out of GET /api/v1/purchases. This page explains which rows are sales, which amount fields are authoritative, how VAT, discounts, modifiers and refunds are represented, and how to run a nightly export that survives late corrections. For direct warehouse access, use the BigQuery guide: its snake-case column names and view availability differ from this REST response.
The shape
A purchase is one payment. It has a status, an amount (total, in cents), a currency, timestamps and a list of line items. Each line item is one thing on the receipt: a product, a tip, or an amount paid toward a split bill.
All money is in integer minor units and all prices are VAT-inclusive.
Which purchases are sales?
Only status === "confirmed". pending is in flight; canceled and failed never took payment. Filter server-side with the status query parameter.
Which line items are products?
type | Meaning | Include in a product-sales export? |
|---|---|---|
product | A sellable catalogue item | Yes |
tip | A gratuity line | No |
amount | An abstract amount paid toward a split or partial bill | No |
Discounts are not separate line items; a line's discount is its appliedTotalDiscountAmountCents. On tabs with a ledger account it holds every discount on the line, given at payment (such as a voucher) or before it (at the till or by an integration's price lock). On other tabs it holds the payment-time discount; a discount given on the item before payment is already in the line's price and has no field of its own.
Prices, quantity and VAT
| Field | What it is |
|---|---|
finalAmountCents | Authoritative per-unit price, VAT-inclusive, after every discount |
totalAmountCents | Total of the entire line before its discount, not a per-unit amount: on ledger tabs the price before every discount, on other tabs with any discount given before payment already in it. May be null on some legacy purchases; reconcile those rows separately |
appliedTotalDiscountAmountCents | The line's discount on the whole line as a positive number (see above). Already taken off finalAmountCents, not off totalAmountCents |
quantity | Whole-unit count |
quantityCents | quantity × 100; divide by 100 for fractional or weight-based items |
vatRateBasisPoints | VAT rate in basis points: 1200 is 12%, 2500 is 25% |
vatAmountCents | VAT per unit, already contained in finalAmountCents |
totalVatAmountCents | VAT for the whole line |
For current finalized lines, the paid line amount is totalAmountCents - appliedTotalDiscountAmountCents. Do not multiply the line total by quantity or subtract the discount again. Legacy/null totals and fractional quantities need a separate check against the purchase payment total.
Modifiers and bundles
Modifiers appear two ways:
- Inline, in
appliedModifications:[{ title, priceAdjustmentCents, variantRef? }]. On tabs outside the tab ledger, payment-time discounts and vouchers also appear here with a negativepriceAdjustmentCents; purchases on ledger tabs carry no discount entries here. Read a line's discount fromappliedTotalDiscountAmountCents, never from these entries. These entries carry no article number. - As child line items, when a variant or add-on maps to its own catalogue item. The child points at its parent through
parentLineItemId.
Avoid double counting
Because add-ons can appear as separate child lines, summing every line can over- or under-count a bundle depending on how the product is modelled. Reconcile your per-purchase line sum against the purchase amount as a check.
Matching a line to your own catalogue
articleNo is a Karma-internal reference used for fiscal cash-register registration. Despite the name it is not your SKU and will not match your catalogue.
Use originItemId, the catalogue item the line was sold from, and join it to the item, where your own identifier lives in the item's metadata.
Refunds
Refund amounts are stored as positive values, the magnitude refunded. Negate them for a ledger. A refund is valid once it is confirmed.
When is a sale final?
There is no single "finalised" flag:
- The payment is final at
status === "confirmed". - Fee and settlement data can be written asynchronously after capture, minutes to a couple of hours later.
- Late corrections and refunds can arrive afterwards.
Export purchases that have been confirmed for a few hours, and re-poll on updatedAt to catch late changes.
Exporting a day's sales
For a nightly job, page through the confirmed purchases for the date with cursor pagination. limit may go up to 1,000, and paginate=cursor skips the expensive total count.
curl "$COMMON_API/api/v1/purchases?locationId=1234&status=confirmed&startDate=2026-09-23T00:00:00Z&endDate=2026-09-24T00:00:00Z&paginate=cursor&limit=500" \
-H "X-API-Key: $KARMA_API_KEY"The response carries data.purchases and data.pagination. While pagination.hasMore is true, repeat the request with cursor=<pagination.nextCursor> and everything else unchanged. Add includeChildren=true to cover a location's descendants in one pass.
Three habits keep the export honest:
- Run it a few hours after close so fee and settlement data has landed, and run it again the next night with
updatedAtas your watermark to pick up refunds and corrections. - Store what you fetched keyed by purchase id, so a job that fails halfway resumes instead of starting over, and a re-fetched purchase overwrites rather than duplicates.
- Mind the rate limit: one request per 500 purchases is cheap; one per purchase is not.
For product lines with current totals, calculate the post-discount line amount as totalAmountCents - appliedTotalDiscountAmountCents and group by vatRateBasisPoints for VAT breakdowns. Reconcile separately with the purchase payment total: it can include tips, unallocated amount payments, and other adjustments, and an article export may intentionally omit non-goods lines.