Skip to content

List the shops this key can access

GET
/v1/shops
curl --request GET \
--url https://api.scanfood.co/ext/v1/shops \
--header 'Authorization: Bearer <token>'

Returns every shop inside the key’s scope. A shop-scoped key returns exactly one row; a franchise-scoped key returns one row per branch. Use shopId from here in /v1/transactions. Each shop also carries its categories, which turn the category ids on bill lines (items[].category) into names.

OK

Media typeapplication/json
object
ok
required
boolean
data
required
Array<object>

One shop (branch) visible to this key.

object
shopId
required

Shop identifier. Use this as shopId on transaction endpoints.

string
shopName
required

Display name of the shop.

string | null
franchiseId
required

Franchise this branch belongs to. null for a standalone shop.

string | null
providerId
required

Membership provider that owns this shop’s member base. Several branches can share one.

string | null
timezone
required

IANA timezone of the shop, e.g. Asia/Bangkok. from/to are calendar days in this zone.

string | null
categories
required

The shop’s product categories as a flat list, so you can turn the ids in a bill line’s items[].category into names. Ordered by level (top-level first), then in the order the shop arranged them. Rebuild the tree with parentId. Every category is listed, including ones the shop has hidden (hidden: true), because older bills can still point at them. Promotional groupings (best sellers, promotions, recommended) are not categories and are not listed; they never appear in items[].category either. [] when the shop has no categories.

Array<object>

One product category of a shop.

object
id
required

Category id — the same value that appears in a bill line’s items[].category.

string
name
required

Category name as the shop set it (the shop’s own language). null when the shop left it empty.

string | null
level
required

Depth in the category tree: 1 = top level, 2 = directly under a top-level category, and so on. null only when the shop’s data carries no readable depth.

integer | null
parentId
required

Id of the category directly above this one. null for a top-level category. It can point at an id that is not in this list: that parent category has since been deleted.

string | null
hidden
required

true when the shop has hidden this category from every sales channel. It is still listed because past bills may reference it.

boolean
serverTime
required

Server clock when the response was produced (UTC, ISO-8601).

string format: date-time
Example
{
"ok": true,
"data": [
{
"shopId": "cQpATdNiK697JkaqHg5k",
"shopName": "ร้านตัวอย่าง สาขาสีลม",
"franchiseId": null,
"providerId": "eExLLv1C1CKb6bQ9GH2S",
"timezone": "Asia/Bangkok",
"categories": [
{
"id": "9369601f-fef5-4a61-bfb8-26f25eab3dad",
"name": "อาหาร",
"level": 1,
"parentId": null,
"hidden": false
},
{
"id": "3c1d7e52-8a0b-4f6e-9d21-5b7a0c4e9f13",
"name": "เครื่องดื่ม",
"level": 1,
"parentId": null,
"hidden": true
},
{
"id": "eff254c9-3d71-4d6f-9e43-1806ece6a427",
"name": "ก๋วยเตี๋ยว",
"level": 2,
"parentId": "9369601f-fef5-4a61-bfb8-26f25eab3dad",
"hidden": false
}
]
}
],
"serverTime": "2026-09-09T13:45:12.310Z"
}
X-RateLimit-Limit
integer

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.

X-RateLimit-Remaining
integer

Requests left in the current Thailand-time day for this key, after this request. Sent under the same conditions as X-RateLimit-Limit.

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).

Media typeapplication/json
object
ok
required
boolean
error
required

Stable machine-readable code. Branch on this, not on message.

string
message

Human-readable explanation. May change without notice.

string
retryAfterSec

Present on rate_limited. Seconds to wait before retrying.

integer
limit

Present on rate_limited. The per-minute ceiling that was hit for this key.

integer
windowSec

Present on rate_limited. Length of the window limit was measured over, in seconds.

integer
Example
{
"ok": false,
"error": "invalid_key",
"message": "Invalid API key"
}

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-*.

Media typeapplication/json
object
ok
required
boolean
error
required

Stable machine-readable code. Branch on this, not on message.

string
message

Human-readable explanation. May change without notice.

string
retryAfterSec

Present on rate_limited. Seconds to wait before retrying.

integer
limit

Present on rate_limited. The per-minute ceiling that was hit for this key.

integer
windowSec

Present on rate_limited. Length of the window limit was measured over, in seconds.

integer
Example
{
"ok": false,
"error": "quota_exceeded",
"message": "daily quota exceeded (10000 requests/day, Thailand time). Resets at 00:00 ICT."
}
Retry-After
integer

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.

Media typeapplication/json
object
ok
required
boolean
error
required

Stable machine-readable code. Branch on this, not on message.

string
message

Human-readable explanation. May change without notice.

string
retryAfterSec

Present on rate_limited. Seconds to wait before retrying.

integer
limit

Present on rate_limited. The per-minute ceiling that was hit for this key.

integer
windowSec

Present on rate_limited. Length of the window limit was measured over, in seconds.

integer
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.

Media typeapplication/json
object
ok
required
boolean
error
required

Stable machine-readable code. Branch on this, not on message.

string
message

Human-readable explanation. May change without notice.

string
retryAfterSec

Present on rate_limited. Seconds to wait before retrying.

integer
limit

Present on rate_limited. The per-minute ceiling that was hit for this key.

integer
windowSec

Present on rate_limited. Length of the window limit was measured over, in seconds.

integer
Example
{
"ok": false,
"error": "auth_unavailable",
"message": "Authentication service temporarily unavailable, retry later"
}