API reference
Orders API
Send an order from your shop, kiosk or bot. Sansflow prices it from the outlet’s menu and the venue accepts it at the till; payment is collected at the counter.
Test mode: an sf_test_ key’s order is validated and priced like a live one and comes back with status: "test" and is_test: true. It never reaches the till, the kitchen, stock or reports, sends no webhooks, and is deleted after 24 hours.
Order status
One word everywhere: the create response, Retrieve an order, and the data of every order.* webhook.
| status | Meaning |
|---|---|
pending_confirmation | Submitted; waiting for the venue to accept it. |
accepted | Accepted; no bill is open for it yet. |
preparing | On an open bill: the kitchen has it. |
ready | The bill is marked ready. |
completed | The bill was closed (payment collected). |
cancelled | The venue rejected it, it expired before anyone accepted it, or its bill was cancelled or voided. |
awaiting_payment | Waiting for an online payment. API orders are paid at the counter, so v1 does not return it today. |
test | Created with an sf_test_ key. |
Create an order
/api-v1-orders-createCreates an unpaid order for one outlet. It waits for the venue to accept it (pending_confirmation), or is accepted straight away when the outlet sends orders to the kitchen automatically and a shift is open.
A live order nobody accepts within 20 minutes expires and becomes cancelled.
Idempotency: the same Idempotency-Key with the same body returns the original order with replay: true (and an empty items); the same key with a different body is 409 idempotency_key_conflict.
The body is checked before the Idempotency-Key header, so a bad body answers 422 even without the header.
Headers
| Name | Type | Required | Description |
|---|---|---|---|
Idempotency-Key | string | Required | A unique key per order, usually a UUID.1-255 characters. |
Request body
| Name | Type | Required | Description |
|---|---|---|---|
location_id | uuid | Required | The outlet the order is for. |
order_type | string | Required | dine_in or takeaway. |
table_ref | string | null | Optional | A table label shown to staff.At most 24 characters. |
external_ref | string | null | Optional | Your own reference, returned on reads and webhooks.At most 255 characters. |
customer.name | string | null | Optional | The guest’s name.At most 120 characters. |
customer.phone | string | null | Optional | The guest’s phone.At most 32 characters. |
items[] | array | Required | The lines.1-50 items. |
items[].item_id | uuid | Required | A product id from the menu. |
items[].variant_id | uuid | null | Optional | One of the product’s variants. |
items[].quantity | integer | Required | How many.1-99. |
items[].notes | string | null | Optional | A note for the kitchen.At most 240 characters. |
items[].modifiers[] | array | Optional | { modifier_id } for each chosen option. Each group’s required, min and max rules apply. |
Response fields (201)
| Name | Type | Description |
|---|---|---|
id | uuid | The order. Keep it to read the order back. |
status | string | The order status; see the status table above. |
queue_number | string | null | The bill number once the till has a bill for it. |
external_ref | string | null | Your reference, as you sent it. |
is_test | boolean | true for an order made with an sf_test_ key. |
totals.gross | integer | The order total in rupiah, priced by Sansflow. |
items[] | array | Each line as priced: product_id, product_name (the variant’s name when a variant was chosen), variant_id, variant_name, quantity, unit_price (with modifiers), base_price, modifiers[] { modifier_id, modifier_name, price, group_id }, notes, line_total. |
created_at | timestamp | When it was created. |
replay | boolean | true when this is the stored order for a repeated Idempotency-Key. |
Errors
Every error answers { "error": { "code", "message", "request_id" } }. Branch on code; see Errors for retrying.
| HTTP | Code | When |
|---|---|---|
| 400 | invalid_json | The POST body is not valid JSON. |
| 422 | validation_failed | The body failed the schema, or one or more lines failed. error.details lists each problem with item_index and a code: item_unavailable, invalid_variant, invalid_modifiers, quantity_invalid, invalid_item, location_not_found, invalid_order_type or empty_cart. |
| 400 | idempotency_key_required | A POST without an Idempotency-Key header (1-255 characters). |
| 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. |
| 409 | idempotency_key_conflict | The Idempotency-Key was already used with a different request. |
| 405 | method_not_allowed | Anything but POST. |
| 500 | internal_error | Something failed on our side. Quote the request_id when you report it. |
curl -X POST "https://ewpuclvmnygoddunkyrb.supabase.co/functions/v1/api-v1-orders-create" \
-H "Authorization: Bearer $SANSFLOW_API_KEY" \
-H "Idempotency-Key: 2b7e4c1a-9d3f-4a6b-8e5c-0f1a2b3c4d5e" \
-H "Content-Type: application/json" \
-d '{
"location_id": "3f6c2a1e-8b4d-4c2a-9e51-7a0d2b6c9f10",
"order_type": "dine_in",
"table_ref": "T-4",
"external_ref": "shop_order_1234",
"customer": {
"name": "Rina",
"phone": "081200000000"
},
"items": [
{
"item_id": "f1b3d5a7-9c2e-4a6b-8d14-7e3c5a9b1d68",
"variant_id": "6e8a0c2d-4f1b-4d5e-8a96-0c2e4a6b8d81",
"quantity": 2,
"notes": "less sugar",
"modifiers": [
{
"modifier_id": "9b1d3f5a-7c2e-4a8b-8d49-3f5b7d9f1a03"
}
]
}
]
}'const res = await fetch(
"https://ewpuclvmnygoddunkyrb.supabase.co/functions/v1/api-v1-orders-create",
{
method: "POST",
headers: {
Authorization: `Bearer ${process.env.SANSFLOW_API_KEY}`,
"Idempotency-Key": "2b7e4c1a-9d3f-4a6b-8e5c-0f1a2b3c4d5e",
"Content-Type": "application/json",
},
body: JSON.stringify({
"location_id": "3f6c2a1e-8b4d-4c2a-9e51-7a0d2b6c9f10",
"order_type": "dine_in",
"table_ref": "T-4",
"external_ref": "shop_order_1234",
"customer": {
"name": "Rina",
"phone": "081200000000"
},
"items": [
{
"item_id": "f1b3d5a7-9c2e-4a6b-8d14-7e3c5a9b1d68",
"variant_id": "6e8a0c2d-4f1b-4d5e-8a96-0c2e4a6b8d81",
"quantity": 2,
"notes": "less sugar",
"modifiers": [
{
"modifier_id": "9b1d3f5a-7c2e-4a8b-8d49-3f5b7d9f1a03"
}
]
}
]
}),
},
);
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.post(
"https://ewpuclvmnygoddunkyrb.supabase.co/functions/v1/api-v1-orders-create",
headers={
"Authorization": f"Bearer {os.environ['SANSFLOW_API_KEY']}",
"Idempotency-Key": "2b7e4c1a-9d3f-4a6b-8e5c-0f1a2b3c4d5e",
},
json={
"location_id": "3f6c2a1e-8b4d-4c2a-9e51-7a0d2b6c9f10",
"order_type": "dine_in",
"table_ref": "T-4",
"external_ref": "shop_order_1234",
"customer": {
"name": "Rina",
"phone": "081200000000"
},
"items": [
{
"item_id": "f1b3d5a7-9c2e-4a6b-8d14-7e3c5a9b1d68",
"variant_id": "6e8a0c2d-4f1b-4d5e-8a96-0c2e4a6b8d81",
"quantity": 2,
"notes": "less sugar",
"modifiers": [
{
"modifier_id": "9b1d3f5a-7c2e-4a8b-8d49-3f5b7d9f1a03"
}
]
}
]
},
timeout=30,
)
data = res.json()
if not res.ok:
raise RuntimeError(f"{data['error']['code']} ({data['error']['request_id']})")
print(data)POST /api-v1-orders-create
{
"id": "a9c1e3f5-7b2d-4f8a-9e63-5c0b4d6a8f57",
"status": "pending_confirmation",
"queue_number": null,
"external_ref": "shop_order_1234",
"is_test": false,
"totals": {
"gross": 60000
},
"items": [
{
"product_id": "f1b3d5a7-9c2e-4a6b-8d14-7e3c5a9b1d68",
"product_name": "Iced",
"variant_id": "6e8a0c2d-4f1b-4d5e-8a96-0c2e4a6b8d81",
"variant_name": "Iced",
"quantity": 2,
"unit_price": 30000,
"base_price": 25000,
"modifiers": [
{
"modifier_id": "9b1d3f5a-7c2e-4a8b-8d49-3f5b7d9f1a03",
"modifier_name": "Extra shot",
"price": 3000,
"group_id": "8a0c2e4f-6b1d-4f7a-9c38-2e4a6c8e0f92"
}
],
"notes": "less sugar",
"line_total": 60000
}
],
"created_at": "2026-11-08T03:05:12.481+00:00",
"replay": false,
"request_id": "5d0c7e2a-91f4-4b3e-a8c6-0f2e4b6d8a1c"
}Example values. Run replays this response here; nothing is sent.
Retrieve an order
/api-v1-orders-get/{order_id}The status, queue number, totals and lines of an order created through the API. The same shape is the data of every order.* webhook.
Path parameters
| Name | Type | Required | Description |
|---|---|---|---|
order_id | uuid | Required | The id the create call returned. |
Response fields (200)
| Name | Type | Description |
|---|---|---|
id | uuid | The order. Keep it to read the order back. |
status | string | The order status; see the status table above. |
queue_number | string | null | The bill number once the till has a bill for it. |
external_ref | string | null | Your reference, as you sent it. |
is_test | boolean | true for an order made with an sf_test_ key. |
totals.gross | integer | The order total in rupiah, priced by Sansflow. |
items[] | array | Each line as priced: product_id, product_name (the variant’s name when a variant was chosen), variant_id, variant_name, quantity, unit_price (with modifiers), base_price, modifiers[] { modifier_id, modifier_name, price, group_id }, notes, line_total. |
created_at | timestamp | When it was created. |
order_type | string | dine_in or takeaway. |
table_ref | string | null | The table label you sent. |
updated_at | timestamp | Last change. |
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 | 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). |
| 405 | method_not_allowed | Anything but GET. |
| 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-orders-get/a9c1e3f5-7b2d-4f8a-9e63-5c0b4d6a8f57" \
-H "Authorization: Bearer $SANSFLOW_API_KEY"const res = await fetch(
"https://ewpuclvmnygoddunkyrb.supabase.co/functions/v1/api-v1-orders-get/a9c1e3f5-7b2d-4f8a-9e63-5c0b4d6a8f57",
{
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-orders-get/a9c1e3f5-7b2d-4f8a-9e63-5c0b4d6a8f57",
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-orders-get/{order_id}
{
"id": "a9c1e3f5-7b2d-4f8a-9e63-5c0b4d6a8f57",
"status": "preparing",
"queue_number": "K-14",
"external_ref": "shop_order_1234",
"order_type": "dine_in",
"table_ref": "T-4",
"is_test": false,
"totals": {
"gross": 60000
},
"items": [
{
"product_id": "f1b3d5a7-9c2e-4a6b-8d14-7e3c5a9b1d68",
"product_name": "Iced",
"variant_id": "6e8a0c2d-4f1b-4d5e-8a96-0c2e4a6b8d81",
"variant_name": "Iced",
"quantity": 2,
"unit_price": 30000,
"base_price": 25000,
"modifiers": [
{
"modifier_id": "9b1d3f5a-7c2e-4a8b-8d49-3f5b7d9f1a03",
"modifier_name": "Extra shot",
"price": 3000,
"group_id": "8a0c2e4f-6b1d-4f7a-9c38-2e4a6c8e0f92"
}
],
"notes": "less sugar",
"line_total": 60000
}
],
"created_at": "2026-11-08T03:05:12.481+00:00",
"updated_at": "2026-11-08T03:06:40.102+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.Events for this resource: Webhooks reference.
Stuck? Quote the request_id from the response and write toSansflow support.Checked against API v1 on .