You are viewing a non-production environment — the API surface may differ from production.

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?

typeMeaningInclude in a product-sales export?
productA sellable catalogue itemYes
tipA gratuity lineNo
amountAn abstract amount paid toward a split or partial billNo

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

FieldWhat it is
finalAmountCentsAuthoritative per-unit price, VAT-inclusive, after every discount
totalAmountCentsTotal 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
appliedTotalDiscountAmountCentsThe line's discount on the whole line as a positive number (see above). Already taken off finalAmountCents, not off totalAmountCents
quantityWhole-unit count
quantityCentsquantity × 100; divide by 100 for fractional or weight-based items
vatRateBasisPointsVAT rate in basis points: 1200 is 12%, 2500 is 25%
vatAmountCentsVAT per unit, already contained in finalAmountCents
totalVatAmountCentsVAT 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 negative priceAdjustmentCents; purchases on ledger tabs carry no discount entries here. Read a line's discount from appliedTotalDiscountAmountCents, 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:

  1. Run it a few hours after close so fee and settlement data has landed, and run it again the next night with updatedAt as your watermark to pick up refunds and corrections.
  2. 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.
  3. 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.

On this page