Get one transaction by id
const url = 'https://api.scanfood.co/ext/v1/transactions/example';const options = {method: 'GET', headers: {Authorization: 'Bearer <token>'}};
try { const response = await fetch(url, options); const data = await response.json(); console.log(data);} catch (error) { console.error(error);}curl --request GET \ --url https://api.scanfood.co/ext/v1/transactions/example \ --header 'Authorization: Bearer <token>'Returns a single bill. A bill belonging to a shop outside this key’s scope returns
404 not_found — the same answer as a bill that does not exist, so the API never confirms
the existence of another shop’s data.
Authorizations
Section titled “Authorizations”Parameters
Section titled “Parameters”Path Parameters
Section titled “Path Parameters”Transaction id, as returned in data[].id.
Responses
Section titled “Responses”OK
object
One bill (transaction) recorded by the POS. Every field below is always present in the
response — a field with nothing to report comes back as null (or [] for the lists),
never missing. Fields not listed here are never sent, whatever the POS stored on the bill.
object
Transaction id. Stable. Use it with /v1/transactions/{id} and as a cursor.
Orders that were settled into this bill. Empty array when the POS recorded none.
Receipt number printed for the customer.
Full tax-invoice number, when one was issued.
When the bill was closed. ISO-8601 with the shop’s own UTC offset, e.g. 2026-09-09T14:03:05.120+07:00.
Accounting day the bill is counted in (YYYY-MM-DD). Taken from the trading-day stamp
the bill carries, or worked out from the bill time and the shop’s own closing time when
that stamp is absent. null only when neither is possible — the bill has no readable
time, or the shop has no closing time configured.
Calendar day in the shop’s timezone (YYYY-MM-DD). This is the field from/to filter on — the request parameters use YYYYMMDD (no dashes) while the response uses dashes.
Last time the bill changed. ISO-8601 with the shop’s own UTC offset. null on bills
written before change tracking existed — those never appear in updatedSince mode.
Sending a bill to an accounting integration does not count as a change and does not
move this field.
Franchise the shop belongs to.
Shop that issued the bill.
Device that closed the bill.
POS station that closed the bill.
Where the sale came from.
object
dine_in — served at a table in the shop.
take_away — rung up at the counter to be taken away.
pickup — ordered for pick-up (QR / self-order link).
Any other value is the shop’s own sales-channel id (delivery apps, marketplaces,
call-in, and so on) exactly as the shop configured it; the readable label is in
channel.name. Treat this as an open string, not a closed enum — shops add channels
at any time. null when the bill carries no table/channel reference.
Readable channel name as configured by the shop (for example หน้าร้าน, Grab).
alacarte on a normal sale; otherwise the id of the buffet package that was sold,
not its display name. Not a fixed set — each shop defines its own packages.
Table name, for dine-in bills.
Opaque, stable member token (prefix m1_) when the bill is linked to a member; null otherwise. The same token is used by the Member API, so you can join the two. It is not the customer’s real identifier and cannot be reversed.
One line on the bill. Every field below is always present.
object
Product id. Join to your own catalogue with this.
Parent product id for variants/set members.
Product name as printed on the bill.
Quantity sold on this line. null when the POS recorded no readable quantity.
Price per unit recorded on the line (THB). Where the POS recorded none it is worked out
as line total ÷ quantity, and is null when even that is not possible (no readable
quantity, quantity 0, or no line total).
Total for this line (THB).
Chosen options/add-ons for this line. Empty array when there are none.
object
Option/choice id.
Option/choice name as printed on the bill.
How many of this option were taken on the line. Defaults to 1 when the POS recorded no quantity.
Category path the product sat in at sale time, as category ids ordered from the top-level category down to the most specific one. Most bills carry one id. Ids, not names — they stay stable when staff rename a category. [] when the product had no category. Turn ids into names with categories from /v1/shops (match on id). An id with no match there belongs to a category the shop has since deleted; its name cannot be recovered.
true when the line was voided during the order; cancelled lines do not contribute to the totals.
Whether this line is VAT-applicable.
true when the line is a tip, not a sold item.
Money on the bill, in THB. Every field below is always present. A money field is null when
the shop does not run that feature at all, which is deliberately different from 0 (“the
feature is on and the amount happened to be zero”).
object
Sum of the item lines before discounts and charges, with tip lines left out. A tip still
appears in items[] with isTip: true, so on a bill that carries a tip this will not
equal the sum of lineTotal.
Manual discount entered by the cashier.
Promotions applied to this bill. Empty array when none were used.
One promotion or coupon applied to the bill.
object
Identifier of the promotion/coupon as configured by the shop.
Readable name of the promotion/coupon.
Discount this line contributed (THB). 0 when it carried no discount amount.
Coupons applied to this bill. Empty array when none were used.
One promotion or coupon applied to the bill.
object
Identifier of the promotion/coupon as configured by the shop.
Readable name of the promotion/coupon.
Discount this line contributed (THB). 0 when it carried no discount amount.
Total discount recorded on the bill (manual + campaign + coupon), as computed by the POS. It is not capped at the amount due, so on a bill where the discounts add up to more than the bill itself, this is larger than the amount that could actually be taken off.
Service charge.
Delivery fee charged to the customer.
Card surcharge charged to the customer.
Store credit redeemed on this bill.
VAT amount.
VAT mode of the bill. The values in use are Include (prices already contain VAT, which
is extracted from the total) and Exclude (VAT is added on top at the end). Shops that
never configured VAT can carry an empty string. Treat it as an open string.
Rounding adjustment.
Tip collected. Included in net but not shop revenue.
Amount the customer paid, including tip.
One payment taken against the bill. A bill can be split across several.
object
Payment method name as configured by the shop (cash, PromptPay, card, …).
Amount tendered through this method (THB) — what the customer handed over, not what was
settled. For cash that can be more than the bill. The amount that actually settled is
amount - change, and it is that figure which adds up to amounts.net across the list.
Change given back on this payment (THB). 0 on methods that give none. null only when
the recorded value cannot be read as a number.
void means the whole bill was cancelled after being closed. Voided bills are still returned — exclude them from revenue yourself.
Reason recorded when the bill was voided.
Name of the staff member who closed the bill.
Server clock when the response was produced (UTC, ISO-8601).
Example
{ "ok": true, "data": { "businessDate": "2026-08-31", "billDate": "2026-09-01", "channel": { "code": "dine_in" }, "items": [ { "options": [ { "qty": 1 } ], "category": [ "9369601f-fef5-4a61-bfb8-26f25eab3dad", "eff254c9-3d71-4d6f-9e43-1806ece6a427" ] } ], "amounts": { "vatType": "Include" }, "payments": [ { "amount": 400, "change": 7.35 } ], "status": "completed" }, "serverTime": "2026-09-09T13:45:12.310Z"}Headers
Section titled “Headers”Daily quota for this key — requests allowed per Thailand-time calendar day.
This is not the per-minute burst limit; hitting that limit returns rate_limited
with a Retry-After header, and is not reflected in these two headers.
Not sent on every response. The pair is added by the daily meter, which runs after the
key check and after the per-minute brake, so 401, 503, and 429 rate_limited carry
neither header. Everything that reaches the meter — including 429 quota_exceeded and
4xx/5xx raised by the handler itself — carries both.
Requests left in the current Thailand-time day for this key, after this request.
Sent under the same conditions as X-RateLimit-Limit.
Malformed request. error is one of:
bad_query (missing/invalid parameters, both or neither selection mode, range over 31 days,
a lineUserId that is not shaped like a LINE user id),
bad_cursor (the cursor is not valid, or points at a row that no longer exists),
not_supported (a parameter that exists in this document is not available yet — today only
updatedSince on /v1/members; the message says what to do instead).
object
Stable machine-readable code. Branch on this, not on message.
Human-readable explanation. May change without notice.
Present on rate_limited. Seconds to wait before retrying.
Present on rate_limited. The per-minute ceiling that was hit for this key.
Present on rate_limited. Length of the window limit was measured over, in seconds.
Example
{ "ok": false, "error": "bad_query", "message": "choose exactly one mode: from+to (calendar days) or updatedSince (ISO-8601)"}Missing or unusable key. error is invalid_key (absent, malformed, or wrong secret — the
API does not reveal whether the key id exists) or key_revoked (the key was revoked).
object
Stable machine-readable code. Branch on this, not on message.
Human-readable explanation. May change without notice.
Present on rate_limited. Seconds to wait before retrying.
Present on rate_limited. The per-minute ceiling that was hit for this key.
Present on rate_limited. Length of the window limit was measured over, in seconds.
Example
{ "ok": false, "error": "invalid_key", "message": "Invalid API key"}not_found — no such bill, or it belongs to a shop outside this key’s scope.
object
Stable machine-readable code. Branch on this, not on message.
Human-readable explanation. May change without notice.
Present on rate_limited. Seconds to wait before retrying.
Present on rate_limited. The per-minute ceiling that was hit for this key.
Present on rate_limited. Length of the window limit was measured over, in seconds.
Example
{ "ok": false, "error": "not_found", "message": "transaction not found"}rate_limited — too many requests per minute for this key. The body carries retryAfterSec,
limit (the per-minute ceiling that was hit) and windowSec (the window it was measured
over), and the response also has a Retry-After header. X-RateLimit-* are not sent on
this one, because the per-minute brake sits in front of the daily meter.
quota_exceeded — the daily quota for this key is used up; it resets at 00:00 Thailand time
(ICT). This one does carry X-RateLimit-*.
object
Stable machine-readable code. Branch on this, not on message.
Human-readable explanation. May change without notice.
Present on rate_limited. Seconds to wait before retrying.
Present on rate_limited. The per-minute ceiling that was hit for this key.
Present on rate_limited. Length of the window limit was measured over, in seconds.
Example
{ "ok": false, "error": "quota_exceeded", "message": "daily quota exceeded (10000 requests/day, Thailand time). Resets at 00:00 ICT."}Headers
Section titled “Headers”Seconds to wait before trying again. Sent on 429 rate_limited (the per-minute brake).
Not sent on 429 quota_exceeded, where the wait is until the next Thailand-time day.
internal — temporary failure on our side. Retry with backoff.
object
Stable machine-readable code. Branch on this, not on message.
Human-readable explanation. May change without notice.
Present on rate_limited. Seconds to wait before retrying.
Present on rate_limited. The per-minute ceiling that was hit for this key.
Present on rate_limited. Length of the window limit was measured over, in seconds.
Example
{ "ok": false, "error": "internal", "message": "temporary failure, please retry"}auth_unavailable — the store we check keys against could not be read, so the request was
refused instead of being let through. Your key is fine; this is our side. Retry with backoff.
X-RateLimit-* are not sent on this response.
object
Stable machine-readable code. Branch on this, not on message.
Human-readable explanation. May change without notice.
Present on rate_limited. Seconds to wait before retrying.
Present on rate_limited. The per-minute ceiling that was hit for this key.
Present on rate_limited. Length of the window limit was measured over, in seconds.
Example
{ "ok": false, "error": "auth_unavailable", "message": "Authentication service temporarily unavailable, retry later"}