Skip to content

Time zone & business date

A restaurant’s day does not end at midnight. ScanFood therefore records two different dates on every bill, and picking the wrong one is the most common reason an export disagrees with the shop’s own report.

Each shop has its own time zone, returned as an IANA name by GET /v1/shops:

{ "shopId": "aK3mP9xQ2bR7cT4dV6wY", "timezone": "Asia/Bangkok" }

The value is null for a shop that never set one, which is the normal state for older shops. Treat null as Asia/Bangkok — that is what ScanFood itself does when it formats that shop’s dates. Do not pass the raw value to a zone parser without that fallback, or the first old branch you meet will break the run.

Every timestamp is formatted for the reader who cares about it:

Field Format Zone
timestamp, updatedAt on a transaction ISO-8601 with an offset, e.g. 2026-09-01T13:30:00.000+07:00 The shop’s zone
createdDate, coupons[].expireAt on a member ISO-8601 with an offset The member brand’s zone
businessDate, billDate, lastVisit YYYY-MM-DD A wall-clock date in that same zone — no time, no offset
serverTime on every response ISO-8601 ending in Z UTC
from / to in a request YYYYMMDD, no dashes Calendar days in the shop’s zone
updatedSince in a request ISO-8601 Any zone you like — send Z and avoid the question

Because the offset travels with the timestamp, you never have to know a shop’s zone to sort or compare bills — parse them as instants. You only need the zone when you want to say “which day was that?”, and for that the API has already done the work: read billDate or businessDate rather than re-deriving a day from the timestamp.

Field Question it answers
billDate On which calendar day was this bill paid, on the clock on the shop’s wall?
businessDate Which trading day’s takings does this bill belong to?

For a shop that closes before midnight the two are always identical. For a bar that stops serving at 03:00 they diverge every single night — and that is the whole point of having both.

Every shop sets a cut-off time: the moment its trading day rolls over. A shop with a 06:00 cut-off treats everything rung up between 06:00 today and 05:59 tomorrow as one trading day — which is exactly how its own end-of-day report, its cash count and its staff think about it.

businessDate applies that rule for you. You do not need to know the cut-off, fetch it, or replicate the arithmetic.

Two rules cover every case:

  1. Filter with from/to, which match billDate. There is no way to filter by business date; the range parameters look at the calendar day only.
  2. Group by businessDate whenever the number has to agree with the shop. Revenue, VAT, daily takings, commission — all of these are trading-day figures.

That combination has one consequence worth designing around: to be sure you hold every bill of a trading day, you must fetch the calendar day and the following one, then group by businessDate. A bill rung up at 01:00 on 2 September carries billDate: "2026-09-02" while belonging to the trading day of 1 September.

The safe pattern for a nightly export:

  • Fetch from = the day you are reporting, to = the day after.
  • Keep only the rows whose businessDate is the day you are reporting.
  • Re-run the day once more after the shop’s cut-off has passed, since late voids and re-issued tax invoices change bills after the fact — the incremental mode described in Pagination & sync catches these automatically.

A shop in Asia/Bangkok with a 06:00 cut-off. Two bills:

Paid at timestamp billDate businessDate
1 Sep, 13:30 2026-09-01T13:30:00.000+07:00 2026-09-01 2026-09-01
2 Sep, 01:40 2026-09-02T01:40:00.000+07:00 2026-09-02 2026-09-01

The shop’s own report for 1 September contains both bills. So:

  • Requesting from=20260901&to=20260901 returns only the first — the second has a September 2 calendar date. Your total is short.
  • Requesting from=20260901&to=20260902 returns both, plus everything that genuinely belongs to 2 September. Filtering that result on businessDate === "2026-09-01" gives you exactly the shop’s number.

If you compare against a shop’s daily report and land a few bills short, this is almost always why.