Get started
Authentication and API keys
Every request carries one API key as a Bearer token. The key decides which business, which data and which outlet the request can see.
Bearer keys
Send the key in the Authorization header on every request:
Authorization: Bearer sf_live_…A key is created in Sansflow at pos.sansflow.com/developers → API keys by someone allowed to manage the API. It carries:
- a mode, test or live (the prefix tells you which);
- one or more scopes, one per resource;
- optionally one outlet it is pinned to.
A missing, malformed or revoked key answers 401 invalid_key. A key without the scope an endpoint needs answers 403 insufficient_scope, and a business that is not on Sansflow Premium answers 402 payment_required (the key itself is fine).
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}` },
});
const body = await res.json();
if (!res.ok) throw new Error(`${body.error.code} (request ${body.error.request_id})`);
console.log(body.data); // the outlets this key may readimport os, requests
res = requests.get(
"https://ewpuclvmnygoddunkyrb.supabase.co/functions/v1/api-v1-locations",
headers={"Authorization": f"Bearer {os.environ['SANSFLOW_API_KEY']}"},
timeout=10,
)
body = res.json()
if not res.ok:
raise RuntimeError(f"{body['error']['code']} (request {body['error']['request_id']})")
print(body["data"]) # the outlets this key may readTest and live keys
| Prefix | Sees | Rate limit |
|---|---|---|
sf_test_ | Test orders only. Orders it creates are priced like real ones, stored as status: "test", never reach the till, kitchen, stock or reports, send no webhooks, and are deleted after 24 hours. | 60 a minute |
sf_live_ | Real data. | 300 a minute |
The two never cross: a live key gets 404 for a test order and a test key gets 404 for a live one.
Pinning a key to one outlet
A key pinned to one outlet cannot read another:
- passing
?location_id=for a different outlet answers403 insufficient_scope; - with no
location_id, every list and the daily report are scoped to the pinned outlet automatically; GET /api-v1-locationsreturns just that outlet.
The check runs twice, at the edge and again in the database, so a pinned key stays pinned. Pin a key whenever an integration serves one branch, such as a booking bot for one café.
Keeping keys safe
- The key is shown once, when it is created. Sansflow keeps only a SHA-256 hash and the first 12 characters (for example
sf_live_9A2b), which is how the app lists it. - Keep keys on your server, in an environment variable or a secret manager. Never ship one in a browser, a mobile app or a public repository.
- If a key may have leaked, revoke it in API keys and create a new one. A revoked key answers
401 invalid_keyfrom then on. - Use a separate key per integration so you can revoke one without stopping the others.
Request IDs
Every response carries an x-request-id header. Successful bodies repeat it as request_id and error bodies as error.request_id. Each call is also recorded in the business's request log (Developers → Request log) with the status you received, how long it took and the error code. Quote the ID when you report a problem.
Stuck? Quote the request_id from the response and write toSansflow support.Checked against API v1 on .