ScanFood
Overview
Read-only access to a shop's own transaction data.
ScanFood External API 1.3.0
Section titled “ScanFood External API 1.3.0”Pull-based, read-only access to transactions (bills) recorded by ScanFood POS.
EN — Authenticate with an API key issued per shop or per franchise. A key only ever sees
the shops inside its own scope; any shopId outside that scope is rejected. All endpoints are
GET, return JSON, and are designed to be polled on a schedule (for example every 15 minutes).
TH — ใช้กุญแจ API ที่ออกให้รายร้านหรือรายแฟรนไชส์ · กุญแจ 1 ใบเห็นเฉพาะสาขาในขอบเขตของตัวเอง
ทุกเส้นเป็น GET คืน JSON และออกแบบมาให้ “ดึงเป็นรอบ” (pull)
Authentication
Send the key in the Authorization header: Authorization: Bearer sf_live_<keyId>_<secret>.
Keys are shown once when issued — ScanFood stores only a hash and cannot show it again.
Treat the key as a password: it belongs on your server, never in a browser or a mobile app
(CORS is deliberately disabled on this API, so browser calls will be blocked by the browser).
Paging
/v1/transactions and /v1/members are paged: each returns at most limit rows (default 100,
values above 200 are quietly reduced to 200). When the page comes back full, nextCursor holds
the cursor for the next page; pass it back as cursor to continue. nextCursor: null means you
reached the end. A cursor pointing at a row that no longer exists returns 400 bad_cursor —
restart that page without a cursor.
A page that happens to be exactly full on the last page returns a nextCursor, and the following
request then returns an empty data with nextCursor: null. That is expected, not an error.
/v1/shops is not paged: it returns every shop in the key’s scope in one response and has no
limit, cursor, or nextCursor.
Two ways to select transactions (choose exactly one per request)
from+to— calendar days in the shop’s own timezone (YYYYMMDD, no dashes, maximum 31 days). The matching field in the response isbillDate, which is formatted with dashes (YYYY-MM-DD), as isbusinessDate.updatedSince— only rows changed at or after an ISO-8601 instant. Use this for incremental sync. Rows written before this API existed have no change marker yet and will not appear in this mode; usefrom/tofor a full sync until your backfill is complete.
Calendar day vs business day
businessDate is the day a bill is counted as revenue by the shop (its accounting day ends
at the shop’s own cut-off time, which is often after midnight). billDate is the plain calendar
day. For a shop that closes its books at 06:00, a bill paid at 02:00 has tomorrow’s billDate
but yesterday’s businessDate. Filtering uses billDate; group by businessDate if you need
to reconcile against the shop’s own daily totals. businessDate is null on bills that were
not closed through the current POS path.
Amounts
All money values are in Thai Baht (THB). net is the amount the customer paid, and it includes
tip — tips are collected on behalf of staff and are not shop revenue, so subtract tip
when you compute the shop’s sales.
Errors
Every failure returns { "ok": false, "error": "<code>", "message": "…" }. Branch on error,
never on message. Besides the usual 4xx codes, an authenticated endpoint can answer
503 auth_unavailable: our key-checking store could not be reached, so the request was refused
rather than let through. That is our side, not your key — retry with backoff and do not change
or reissue anything.
Authentication
Section titled “Authentication”ApiKey
Section titled “ApiKey”Authorization: Bearer sf_live_<keyId>_<secret>.
Issued per shop or per franchise. Shown once at issue time; revocable at any time.
A revoked key returns 401 key_revoked; any other failure returns 401 invalid_key.
Security scheme type: http
Bearer format: sf_live_<keyId>_<secret>