API v1
API reference
15 endpoints over HTTPS and JSON, plus signed webhooks. Read sales, the guest book, bookings and the daily report; create orders the till confirms; decide on booking requests.
The basics
- Base URL
https://ewpuclvmnygoddunkyrb.supabase.co/functions/v1- Auth
Authorization: Bearer sf_live_…orsf_test_…. Authentication- Plan
- API access is part of Sansflow Premium. Without it calls answer
402 payment_required. - Format
- JSON. Money is integer rupiah. Timestamps are ISO 8601 with offset.
- Rate limit
- 60 requests a minute per test key, 300 per live key. Rate limits
- Request id
- Every response has an
x-request-idheader; error bodies carry it aserror.request_id.
Your first call
Create a key in the Sansflow app under Settings → API keys (owners only; the key is shown once). Start with an sf_test_ key: it sees only test data and its orders never reach the till.
Keep the key on your server in an environment variable. Every sample on these pages reads it from SANSFLOW_API_KEY.
Getting started walks through it step by step.
curl "https://ewpuclvmnygoddunkyrb.supabase.co/functions/v1/api-v1-locations" \
-H "Authorization: Bearer $SANSFLOW_API_KEY"const res = await fetch("https://ewpuclvmnygoddunkyrb.supabase.co/functions/v1/api-v1-locations", {
headers: { Authorization: `Bearer ${process.env.SANSFLOW_API_KEY}` },
});
console.log(await res.json());import os, requests
res = requests.get(
"https://ewpuclvmnygoddunkyrb.supabase.co/functions/v1/api-v1-locations",
headers={"Authorization": f"Bearer {os.environ['SANSFLOW_API_KEY']}"},
timeout=30,
)
print(res.json())Endpoints
- GET List transactions
- GET Retrieve a transaction
- GET List customers
- GET Retrieve a customer
- GET List reservations
- GET List bookings
- GET Retrieve a booking
- GET Retrieve availability
- POST Confirm a booking
- POST Reject a booking
- GET Retrieve the daily report
- GET List locations
- GET Retrieve the menu
- POST Create an order
- GET Retrieve an order
- 15 events: orders, bookings, reservations
| Method | Endpoint | Scope |
|---|---|---|
| Transactions | ||
| GET | List transactions/api-v1-transactions | transactions.read |
| GET | Retrieve a transaction/api-v1-transactions/{transaction_id} | transactions.read |
| Customers | ||
| GET | List customers/api-v1-customers | customers.read |
| GET | Retrieve a customer/api-v1-customers/{customer_id} | customers.read |
| Reservations | ||
| GET | List reservations/api-v1-reservations | reservations.read |
| Bookings and availability | ||
| GET | List bookings/api-v1-bookings | reservations.read |
| GET | Retrieve a booking/api-v1-bookings/{booking_id} | reservations.read |
| GET | Retrieve availability/api-v1-availability | reservations.read |
| POST | Confirm a booking/api-v1-bookings/{booking_id}/confirm | reservations.write |
| POST | Reject a booking/api-v1-bookings/{booking_id}/reject | reservations.write |
| Daily report | ||
| GET | Retrieve the daily report/api-v1-reports-daily | reports.read |
| Locations | ||
| GET | List locations/api-v1-locations | business.read |
| Menu | ||
| GET | Retrieve the menu/api-v1-menu | menu.read |
| Orders | ||
| POST | Create an order/api-v1-orders-create | orders.create |
| GET | Retrieve an order/api-v1-orders-get/{order_id} | orders.read |
Scopes
A key carries only the scopes you tick when you create it, and can be pinned to one outlet. Give each integration the least it needs: an accounting sync is transactions.read + reports.read; a booking bot is reservations.read + reservations.write.
| Scope | Grants | Endpoints |
|---|---|---|
menu.read | The menu as one outlet sells it | Retrieve the menu |
orders.create | Creating orders the till confirms | Create an order |
orders.read | Reading back orders the API created | Retrieve an order |
transactions.read | Till transaction history and detail | List transactions, Retrieve a transaction |
customers.read | The guest book | List customers, Retrieve a customer |
reservations.read | Reservations, incoming booking requests and slot availability | List reservations, List bookings, Retrieve a booking, Retrieve availability |
reservations.write | Confirming and rejecting booking requests | Confirm a booking, Reject a booking |
reports.read | The daily sales summary | Retrieve the daily report |
business.read | The outlets the key may read | List locations |
A key pinned to one outlet cannot read another: another outlet’s location_id is 403, and without a filter every list and the daily report are scoped to the pinned outlet.
Conventions
Lists and pagination
Lists return data, has_more and next_cursor, newest first. Send next_cursor back as ?cursor= until has_more is false. limit defaults to 50; values above 100 are capped at 100. Keyset paging means a page never repeats or skips a row while new rows arrive. Pagination
Retrieve calls
A single object comes back as { "data": { … }, "request_id" }. The menu and the two orders endpoints predate that envelope: their body is the object itself.
Writes
Every POST needs an Idempotency-Key header. Repeat a request with the same key and you get the first answer back; reuse a key with a different body and you get 409. Idempotency
{
"data": [
"…"
],
"has_more": true,
"next_cursor": "MjAyNi0xMS0wNVQxMjo0MTowMy4yMTRafGIxYTdjOWUy",
"request_id": "5d0c7e2a-91f4-4b3e-a8c6-0f2e4b6d8a1c"
}Errors
Errors use HTTP status codes and a stable error.code. Branch on the code, never on the message. Only answered calls (status below 400, and the menu’s 304) count toward your plan’s API usage. Errors
{
"error": {
"code": "insufficient_scope",
"message": "Key is missing required scope: customers.read",
"request_id": "5d0c7e2a-91f4-4b3e-a8c6-0f2e4b6d8a1c"
}
}| HTTP | Code | Meaning |
|---|---|---|
| 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. |
| 400 | validation_failed | A query parameter did not parse; the message names it. Also returned when from/to span more than 400 days. |
| 400 | invalid_request | The id in the path is not a UUID. |
| 400 | invalid_json | The POST body is not valid JSON. |
| 400 | idempotency_key_required | A POST without an Idempotency-Key header (1-255 characters). |
| 409 | idempotency_key_conflict | The Idempotency-Key was already used with a different request. |
| 404 | not_found | Unknown path under this endpoint. |
| 405 | method_not_allowed | Wrong HTTP method for this endpoint. |
| 404 | transaction_not_found | No such transaction for this key: another business, deleted, or on the other side of the test/live split. |
| 404 | customer_not_found | No such customer in this business’s guest book. |
| 404 | booking_not_found | No such booking for this business or, for an outlet-pinned key, for that outlet. |
| 404 | order_not_found | No order created through the API with this id for this key (test keys see only test orders, live keys only live ones). |
| 404 | location_not_found | location_id is unknown or inactive for this business. |
| 404 | table_not_found | table_id is not an active table at the booking’s outlet. |
| 409 | capacity_full | The slot has no free capacity left. The booking stays pending. |
| 409 | table_taken | The named table already has a reservation overlapping that time. |
| 409 | proof_required | The booking has a pre-order and no transfer proof yet. It stays pending. |
| 409 | booking_not_confirmable | The booking is rejected or expired; only pending bookings can be confirmed. |
| 409 | booking_not_rejectable | The booking is confirmed or expired; only pending bookings can be rejected. |
| 500 | internal_error | Something failed on our side. Quote the request_id when you report it. |
| 422 | validation_failed | On a POST: the body failed validation; error.details lists the problems. |
Stuck? Quote the request_id from the response and write toSansflow support.Checked against API v1 on .