Changelog
Every change to the API that a caller can observe is recorded here, newest first, in the Keep a Changelog format. If something you rely on changes, this is the page it will be announced on.
How we version
Section titled “How we version”The version lives in the path. Today every endpoint sits under /v1/, and the
OpenAPI document reports its own version in info.version — fetch
https://api.scanfood.co/ext/v1/openapi.json if you want to check
programmatically which contract you are working against.
Changes that can arrive in /v1/ at any time:
- a new endpoint;
- a new optional query parameter;
- a new field in a response object;
- a new
errorcode for a situation that previously returned a more general one.
These are additive: an integration that ignores what it does not recognise keeps working. That is also the requirement they place on you — parse leniently. A client that rejects an unknown field will break on a release that breaks nobody else.
Changes that would not arrive in /v1/ in place: removing a field, renaming
one, narrowing what an endpoint accepts, or changing the meaning of a value
already in use. The version sits in the path precisely so that a contract this
different arrives as a new path rather than underneath an existing integration.
Notice period for breaking changes
Section titled “Notice period for breaking changes”A breaking change is announced at least 30 days in advance, on this page
and through your ScanFood contact, before the new path goes live. The existing
/v1/ path is not changed underneath you during that period.
What you can rely on:
- Breaking changes are not made in place under
/v1/. - Anything observable that does change is recorded here with a date.
- Your key keeps working across releases — there is no version pinned to a key, and nothing expires on its own.
Releases
Section titled “Releases”1.3.0 — 2026-09-29
Section titled “1.3.0 — 2026-09-29”Added
categorieson every shop returned byGET /v1/shops— the branch’s product categories as a flat list of{ id, name, level, parentId, hidden }. Theidis the same value that appears in a bill line’sitems[].category, so category ids on bills can now be turned into names and full paths. Every category is listed, including ones the shop has hidden (hidden: true), because older bills can still refer to them. An id on a bill that is not in the list belongs to a category the shop has deleted; its name cannot be recovered. See Shops.
Nothing was removed, renamed or narrowed in this release. The category list is the only catalogue data exposed; products, menus, stock, costs and recipes are still not available through any endpoint.
1.2.1 — 2026-09-28
Section titled “1.2.1 — 2026-09-28”Fixed
rankLevelon members is now always a whole number ornull, as the reference has always declared. It was previously passed through as stored, so most members came back with a string — usually"", sometimes a number in quotes such as"11". A number in quotes now arrives as that number ("11"→11); an empty string, or a value that is not a valid tier position (such as"01"), arrives asnull. If your code already treated""as “no tier”, it keeps working; if it parsed the string, it can now read the number directly.rankon members is now always a string ornull. A handful of old member records held a number there; those now arrive asnull.
Documentation
items[].categoryon bills is documented as what it has always been: an array of category ids, from the top-level category down to the most specific one (usually a single id;[]when the product had no category). The reference and examples previously described it as a category name. The data itself has not changed.
1.2.0 — 2026-09-18
Section titled “1.2.0 — 2026-09-18”Added
GET /v1/members/lookup— the member token behind a LINE user id you already hold. Give it aproviderIdand alineUserId, and it answers with that person’smemberId— the same token that appears on bills and in member listings — so a chat bot can go from “someone just messaged us” to their tier, points and purchase history. The exchange runs one way only: an id goes in, a token comes out, and no response carries a LINE user id back. See Members.- A per-key permission for that lookup,
allowLineLookup, off by default and granted the same waytelandemailare. It gates only the new endpoint; nothing about existing responses changes when it is on or off. 403 lookup_not_enabled— the answer when a key without that permission calls the lookup.
Nothing was removed, renamed or narrowed in this release. An integration that does not use the new endpoint needs no change.
1.1.1 — 2026-09-10
Section titled “1.1.1 — 2026-09-10”Fixed
- Take-away bills now report
channel.codeas"take_away". They were previously reported as"dine_in", withchannel.nameshowing the shop’s price-tier name instead of a channel name, so take-away sales were counted as eat-in. Eat-in, pick-up and shop-defined channels are unchanged. If you have already stored bills from this API, take-away sales inside them are on thedine_inline and can be moved by re-pulling that date range.
1.1.0 — 2026-09-10
Section titled “1.1.0 — 2026-09-10”Added
GET /v1/usage— where your key stands on its quota, read straight from the counters that enforce it: the per-minute and per-day ceilings the key actually runs under, how many requests it has spent today and how many are left, and the last seven Thailand-time days. It takes no parameters and reports only on the key that calls it. See Rate limits.
Changed
- Usage records are no longer kept indefinitely. The record of an individual
request is kept for 90 days, and the daily totals that
/v1/usagereads are kept for 400 days — long enough to compare a month against the same month last year. Nothing else about a key or its data is affected.
1.0.0 — 2026-09-10
Section titled “1.0.0 — 2026-09-10”First public release.
Added
GET /v1/shops— the shops a key can see, withshopId,shopName,franchiseId,providerIdand the shop’stimezone. Not paginated.GET /v1/transactions— bills for one shop, in two modes: a calendar range (from/to, up to 31 days per request) or an incremental pull (updatedSince). Cursor paging withlimitup to 200, default 100.GET /v1/transactions/{id}— one bill in full: line items, amounts, payments, channel, cashier and void status.businessDateon every bill, alongsidebillDate, so shops that trade past midnight can be reconciled on their own trading day.GET /v1/members— loyalty members for one brand (providerId), with points, credit, rank, coupons, labels and visit history. Cursor paging.GET /v1/members/{memberToken}— one member, addressed by the opaque token returned in listings.GET /v1/openapi.json— the full OpenAPI 3.1 document, public and unauthenticated.- API keys in the form
sf_live_<keyId>_<secret>, sent asAuthorization: Bearer …, each scoped to a single shop or to a whole franchise, each with its own label, per-minute limit and daily quota. - Rate limiting and quota reporting:
X-RateLimit-LimitandX-RateLimit-Remainingfor the daily quota,Retry-Afteron a per-minute429, and a daily counter that resets at 00:00 Thailand time. - Key revocation, effective within five minutes, after which the key answers
401 key_revoked. api.scanfood.coas the single host for the API and for this documentation.- This documentation site, including machine-readable copies of every guide.
Known limitations at 1.0.0
updatedSinceis not available on/v1/members; it returns400 not_supported. Members are synchronised in full, page by page.- Bills closed before change tracking existed carry no
updatedAtand therefore never appear inupdatedSinceresults — backfill withfrom/tofirst. - No webhooks and no push: everything is pulled on your schedule.
- No sandbox and no test dataset — see Test data.
- Cross-origin requests are refused; the API is server-to-server only.
- Products, menus, stock, costs and recipes are not exposed by any endpoint.
(Since 1.3.0, the shop’s category list is available on
/v1/shops; the rest of this still holds.)