# Reading sales data (/docs/sales-data)



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](/docs/bigquery): its snake-case column names and view availability differ from this REST response.

## The shape [#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? [#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? [#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 [#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-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`.

<Callout type="warn" title="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.
</Callout>

## Matching a line to your own catalogue [#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 [#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? [#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 [#exporting-a-days-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.

```bash
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](/docs/rate-limits)**: 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.
