Get one member by token
const url = 'https://api.scanfood.co/ext/v1/members/m1_Zm9vYmFyYmF6';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/members/m1_Zm9vYmFyYmF6 \ --header 'Authorization: Bearer <token>'Returns one member. Use the memberId token you got from /v1/members or from a bill’s
memberId field — that is how you join a transaction to the customer who earned the points.
Anything you cannot read returns 404 not_found with the same message: a token that is not
valid, a member who does not exist, and a member belonging to a provider outside this key’s
scope are indistinguishable on purpose. Raw internal identifiers are rejected as well.
Authorizations
Section titled “Authorizations”Parameters
Section titled “Parameters”Path Parameters
Section titled “Path Parameters”The opaque member token (prefix m1_).
Responses
Section titled “Responses”OK
object
One loyalty member. Personal profile fields are deliberately left out: no linked social account
identifier, no birth date, no sex, no photo, no internal staff notes, and no per-bill history.
Phone and email are reported only as the booleans hasTel/hasEmail, unless your key is
explicitly allowed to read them (see tel/email below).
object
Opaque, stable member token (prefix m1_). The same value appears on bills, so you can join the two. It is not the customer’s real identifier and cannot be reversed.
Membership rank code recorded on the member, e.g. BRONZE. null when none is recorded.
Tier index (a whole number, 0-based) within the provider’s tier list. Meaningful only when the provider runs automatic tier upgrades. null when the member has no valid tier index recorded. Use rankName for display.
Display name of the tier the member is on right now. null if the provider has no tiers configured.
Points available to spend, after expiry has been applied. This is not the sum of everything ever earned.
Store credit available to spend, after expiry has been applied. Always 0 when creditEnabled is false.
Whether this provider runs the store-credit programme at all.
Coupons the member holds, including ones already used.
One coupon the member is holding or has already used.
object
The short code the member shows at the counter. Unique per issued coupon.
available = still usable, used = already redeemed.
The coupon definition this was issued from. Several coupons share one template.
Coupon name as the member sees it.
Expiry, ISO-8601 with a +07:00 (Thailand) offset — membership bases do not carry a
timezone of their own, so member times are always Thailand time. null means no expiry
was set.
Free-text tags the shop put on this member.
Free-text persona tags the shop put on this member.
When the member signed up, ISO-8601 with a +07:00 (Thailand) offset — membership bases
do not carry a timezone of their own, so member times are always Thailand time.
How many recorded visits the member has. The visits themselves are not returned.
Date of the most recent recorded visit (YYYY-MM-DD). null if there are none.
Whether a phone number is on file. The number itself is not returned unless your key is allowed to read it.
Whether an email address is on file.
Whether the member is linked to a LINE account.
Phone number. Present only if your key is explicitly allowed to read personal contact details; ask ScanFood to enable it.
Email address. Present only if your key is explicitly allowed to read personal contact details.
Server clock when the response was produced (UTC, ISO-8601).
Example
{ "ok": true, "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.
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 — the token could not be read, there is no member behind it, or the member
belongs to a membership base outside this key’s scope. All three answer the same way on
purpose, so the response cannot be used to find out which members exist.
/v1/members/lookup answers with this same body when the LINE user id matches nobody in
that provider, for the same reason.
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": "member 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"}