API reference
Transactions API
Every sale rung up on the till, newest first. Use it for accounting syncs and data warehouses.
Deleted orders are never returned. Refunded orders are returned with totals.refund above 0 and net = total - refund. For sales as the dashboard reports them, use the daily report instead.
List transactions
/api-v1-transactionsReturns till transactions, newest first by created_at, a page at a time.
A live key never sees test orders and a test key sees only them.
status and payment_method are exact matches on the stored value (lowercase, letters, digits and underscores, up to 40 characters). An unknown value matches nothing rather than failing. Payment methods include cash, qris, edc, edc_bca, card_transfer, card_transfer_bni, card_transfer_bri, card_transfer_bsi, card_transfer_mandiri, pay_later and complimentary.
Query parameters
| Name | Type | Required | Description |
|---|---|---|---|
from | string | Optional | Earliest created_at, inclusive. ISO 8601 with offset, or YYYY-MM-DD.A bare date means 00:00 UTC that day, so pass a full timestamp with +07:00 to cut at local midnight. |
to | string | Optional | Latest created_at, inclusive. ISO 8601 with offset, or YYYY-MM-DD.At most 400 days after from. A bare date means 00:00 UTC that day. |
location_id | uuid | Optional | Only this outlet. A key pinned to one outlet is always scoped to it; another outlet’s id is 403. |
status | string | Optional | Exact match on the order status, for example completed. |
payment_method | string | Optional | Exact match on the payment method, for example qris. |
cursor | string | Optional | The next_cursor from the previous page. Opaque; send it back unchanged. |
limit | integer | Optional | Rows per page.Default 50. At least 1; values above 100 are capped at 100. |
Response fields (200)
| Name | Type | Description |
|---|---|---|
data[] | array | Transactions on this page. |
data[].number | string | The order number printed on the receipt. |
data[].status | string | The order status. |
data[].payment_status | string | The payment status. |
data[].payment_method | string | The tender, as stored. |
data[].sale_type | string | The sale type kind (dine_in, takeaway…). |
data[].sale_type_name | string | Its display name at the outlet. |
data[].source | string | Where the order came from (pos…). |
data[].bill_name | string | null | The bill’s name, for example a table. |
data[].totals | object | total, refund, net, tax, service_charge, payment_fee and bill_discount, all integer rupiah. |
data[].voided_at | timestamp | null | Set when the sale was voided. |
has_more | boolean | true when another page exists. |
next_cursor | string | null | Send it back as cursor for the next page. |
request_id | string | This request’s id, also in the x-request-id header. |
Errors
Every error answers { "error": { "code", "message", "request_id" } }. Branch on code; see Errors for retrying.
| HTTP | Code | When |
|---|---|---|
| 400 | validation_failed | A query parameter did not parse; the message names it. Also returned when from/to span more than 400 days. |
| 401 | invalid_key | The Authorization header is missing, malformed, or the key is revoked or inactive. |
| 402 | payment_required | The business’s plan does not include API access (Sansflow Premium). The key itself is fine. |
| 403 | insufficient_scope | The key does not carry the scope this endpoint needs, or it is pinned to a different outlet. |
| 429 | rate_limited | Over the key’s per-minute limit (60 for sf_test_, 300 for sf_live_). Wait the seconds in Retry-After. |
| 500 | internal_error | Something failed on our side. Quote the request_id when you report it. |
curl -G "https://ewpuclvmnygoddunkyrb.supabase.co/functions/v1/api-v1-transactions" \
-H "Authorization: Bearer $SANSFLOW_API_KEY" \
--data-urlencode "from=2026-11-01T00:00:00+07:00" \
--data-urlencode "to=2026-11-30T23:59:59+07:00" \
--data-urlencode "limit=100"const url = new URL("https://ewpuclvmnygoddunkyrb.supabase.co/functions/v1/api-v1-transactions");
url.search = new URLSearchParams({
"from": "2026-11-01T00:00:00+07:00",
"to": "2026-11-30T23:59:59+07:00",
"limit": "100"
}).toString();
const res = await fetch(
url,
{
headers: {
Authorization: `Bearer ${process.env.SANSFLOW_API_KEY}`,
},
},
);
const json = await res.json();
if (!res.ok) throw new Error(`${json.error.code} (${json.error.request_id})`);
console.log(json);import os
import requests
res = requests.get(
"https://ewpuclvmnygoddunkyrb.supabase.co/functions/v1/api-v1-transactions",
headers={
"Authorization": f"Bearer {os.environ['SANSFLOW_API_KEY']}",
},
params={
"from": "2026-11-01T00:00:00+07:00",
"to": "2026-11-30T23:59:59+07:00",
"limit": "100",
},
timeout=30,
)
data = res.json()
if not res.ok:
raise RuntimeError(f"{data['error']['code']} ({data['error']['request_id']})")
print(data)GET /api-v1-transactions
{
"data": [
{
"id": "b1a7c9e2-4d3f-4a8b-9c61-2e5f7d8a0b13",
"number": "ORD-20261105-3F2A",
"status": "completed",
"payment_status": "paid",
"payment_method": "qris",
"sale_type": "dine_in",
"sale_type_name": "Dine in",
"source": "pos",
"location_id": "3f6c2a1e-8b4d-4c2a-9e51-7a0d2b6c9f10",
"customer_id": null,
"bill_name": "Meja 4",
"totals": {
"total": 50000,
"refund": 0,
"net": 50000,
"tax": 0,
"service_charge": 0,
"payment_fee": 0,
"bill_discount": 0
},
"is_test": false,
"voided_at": null,
"created_at": "2026-11-05T12:41:03.214+00:00"
}
],
"has_more": true,
"next_cursor": "MjAyNi0xMS0wNVQxMjo0MTowMy4yMTRafGIxYTdjOWUy",
"request_id": "5d0c7e2a-91f4-4b3e-a8c6-0f2e4b6d8a1c"
}Example values. Run replays this response here; nothing is sent.
Retrieve a transaction
/api-v1-transactions/{transaction_id}One transaction in full: line items with modifiers, every payment taken, the bill it came from, loyalty and the void reason.
Query filters are ignored here; an outlet-pinned key can only read its own outlet’s transactions.
Path parameters
| Name | Type | Required | Description |
|---|---|---|---|
transaction_id | uuid | Required | The transaction’s id from the list. |
Response fields (200)
| Name | Type | Description |
|---|---|---|
location | object | null | { id, name } of the outlet. The list returns location_id instead. |
bill | object | null | id, number, name, table, zone, party_size, opened_at, closed_at. |
totals | object | The list’s totals plus cash_received and change. |
loyalty | object | points_earned and points_redeemed. |
items[] | array | Top-level lines: id, product_id, name, sku, category, variant_id, variant, quantity, unit_price, discount, line_total, modifiers, notes. |
payments[] | array | Each tender: method, amount, fee, card_last4, reference, created_at. |
void_reason | string | null | Why it was voided. |
Errors
Every error answers { "error": { "code", "message", "request_id" } }. Branch on code; see Errors for retrying.
| HTTP | Code | When |
|---|---|---|
| 400 | invalid_request | The id in the path is not a UUID. |
| 401 | invalid_key | The Authorization header is missing, malformed, or the key is revoked or inactive. |
| 402 | payment_required | The business’s plan does not include API access (Sansflow Premium). The key itself is fine. |
| 403 | insufficient_scope | The key does not carry the scope this endpoint needs, or it is pinned to a different outlet. |
| 429 | rate_limited | Over the key’s per-minute limit (60 for sf_test_, 300 for sf_live_). Wait the seconds in Retry-After. |
| 404 | transaction_not_found | No such transaction for this key: another business, deleted, or on the other side of the test/live split. |
| 404 | not_found | More than one path segment after the endpoint. |
| 500 | internal_error | Something failed on our side. Quote the request_id when you report it. |
curl "https://ewpuclvmnygoddunkyrb.supabase.co/functions/v1/api-v1-transactions/b1a7c9e2-4d3f-4a8b-9c61-2e5f7d8a0b13" \
-H "Authorization: Bearer $SANSFLOW_API_KEY"const res = await fetch(
"https://ewpuclvmnygoddunkyrb.supabase.co/functions/v1/api-v1-transactions/b1a7c9e2-4d3f-4a8b-9c61-2e5f7d8a0b13",
{
headers: {
Authorization: `Bearer ${process.env.SANSFLOW_API_KEY}`,
},
},
);
const json = await res.json();
if (!res.ok) throw new Error(`${json.error.code} (${json.error.request_id})`);
console.log(json);import os
import requests
res = requests.get(
"https://ewpuclvmnygoddunkyrb.supabase.co/functions/v1/api-v1-transactions/b1a7c9e2-4d3f-4a8b-9c61-2e5f7d8a0b13",
headers={
"Authorization": f"Bearer {os.environ['SANSFLOW_API_KEY']}",
},
timeout=30,
)
data = res.json()
if not res.ok:
raise RuntimeError(f"{data['error']['code']} ({data['error']['request_id']})")
print(data)GET /api-v1-transactions/{transaction_id}
{
"data": {
"id": "b1a7c9e2-4d3f-4a8b-9c61-2e5f7d8a0b13",
"number": "ORD-20261105-3F2A",
"status": "completed",
"payment_status": "paid",
"payment_method": "qris",
"sale_type": "dine_in",
"sale_type_name": "Dine in",
"source": "pos",
"location": {
"id": "3f6c2a1e-8b4d-4c2a-9e51-7a0d2b6c9f10",
"name": "Main"
},
"customer_id": null,
"bill": {
"id": "4a6c8e0b-2d3f-4e5a-8b17-6c8e0a2b4c14",
"number": "N8",
"name": "Meja 4",
"table": "T4",
"zone": "Indoor",
"party_size": 3,
"opened_at": "2026-11-05T11:58:20+00:00",
"closed_at": "2026-11-05T12:41:03+00:00"
},
"bill_name": "Meja 4",
"totals": {
"total": 50000,
"refund": 0,
"net": 50000,
"tax": 0,
"service_charge": 0,
"payment_fee": 0,
"bill_discount": 0,
"cash_received": null,
"change": null
},
"loyalty": {
"points_earned": 0,
"points_redeemed": 0
},
"items": [
{
"id": "6f8b0d2e-4a1c-4e3f-9b57-1d3f5b7d9e36",
"product_id": "f1b3d5a7-9c2e-4a6b-8d14-7e3c5a9b1d68",
"name": "Kopi Susu",
"sku": null,
"category": "Kopi",
"variant_id": null,
"variant": null,
"quantity": 2,
"unit_price": 25000,
"discount": 0,
"line_total": 50000,
"modifiers": [],
"notes": null
}
],
"payments": [
{
"method": "qris",
"amount": 50000,
"fee": 0,
"card_last4": null,
"reference": null,
"created_at": "2026-11-05T12:41:02+00:00"
}
],
"is_test": false,
"voided_at": null,
"void_reason": null,
"created_at": "2026-11-05T12:41:03.214+00:00"
},
"request_id": "5d0c7e2a-91f4-4b3e-a8c6-0f2e4b6d8a1c"
}Example values. Run replays this response here; nothing is sent.
Base URL, keys and conventions: API reference overview.
Stuck? Quote the request_id from the response and write toSansflow support.Checked against API v1 on .