Skip to content

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.

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.

Terminal window
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
}

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 cursor
let 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)

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.

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.

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.

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.