Recipe: export yesterday's sales
Call GET /sales with completed_from and completed_to set to yesterday’s window, then follow next_cursor until has_more is false.
How do I filter to one day
Section titled “How do I filter to one day”completed_from and completed_to are ISO-8601 datetimes filtered against completed_at. They must be in UTC and end in Z (for example 2026-09-23T00:00:00Z); an offset such as -04:00 is refused. Both ends are inclusive, so end the window one second early to keep a sale at exactly midnight out of two days. Run this once a night for the previous calendar day in your store’s own timezone, converted to UTC.
curl "https://api.oletapp.com/your-store/v1/sales?completed_from=2026-09-23T00:00:00Z&completed_to=2026-09-23T23:59:59Z&limit=100" \ -H "Authorization: Bearer olet_live_YOUR_API_KEY"{ "data": [ { "id": "...", "status": "completed", "completed_at": "2026-09-23T14:02:11Z", "outlet_id": "...", "version": "..." } ], "next_cursor": "...", "has_more": true}How do I get every page
Section titled “How do I get every page”Keep calling with the last next_cursor you received until has_more is false.
const base = 'https://api.oletapp.com/your-store/v1/sales'let cursorlet sales = []do { const url = new URL(base) url.searchParams.set('completed_from', '2026-09-23T00:00:00Z') url.searchParams.set('completed_to', '2026-09-23T23:59:59Z') url.searchParams.set('limit', '100') if (cursor) url.searchParams.set('cursor', cursor) const page = await (await fetch(url, { headers: { Authorization: 'Bearer olet_live_YOUR_API_KEY' } })).json() sales = sales.concat(page.data) cursor = page.next_cursor if (!page.has_more) break} while (cursor)What is in each row
Section titled “What is in each row”GET /sales returns the same full sale document as GET /sales/{id}: line items, discounts, adjustments, taxes and payments. There is no slimmer summary shape, so you rarely need the single-sale read in an export loop. Totals such as total, tax_total and paid_total are integer cents; see money and quantities.
You can also narrow the list with status (completed, partially_refunded, refunded or voided), outlet_id, customer_id and parent_sale_id.
Does this ever include a refund
Section titled “Does this ever include a refund”Yes. A refund is a sale with parent_sale_id set. Add parent_sale_id as a query filter if you want only the refunds of one original sale, or leave it out to get both original sales and their refunds in the same window.
What is deliberately excluded
Section titled “What is deliberately excluded”status=open is refused with 400 VALIDATION_FAILED: a parked sale is register state mid-transaction, not something to export. The list already excludes it by default, so this only matters if you pass status explicitly. include_deleted is also refused: sales are never deleted, so there is nothing to include.
A sale that is refunded later moves to a new version, so a later poll that uses the cursor will return it again with its new status. If you export by date window instead, re-export recent days to catch late refunds.
Where to go next
Section titled “Where to go next”See pagination and polling for what next_cursor and has_more mean in general, or the full API reference for every field on a sale.