Skip to content

Find a member by LINE user id

GET
/v1/members/lookup
curl --request GET \
--url 'https://api.scanfood.co/ext/v1/members/lookup?providerId=eExLLv1C1CKb6bQ9GH2S&lineUserId=Ua1b2c3d4a1b2c3d4a1b2c3d4a1b2c3d4' \
--header 'Authorization: Bearer <token>'

Translates a LINE user id into the opaque memberId used everywhere else in this API, so a LINE bot can recognise the person it is chatting with. It returns the identifier only — read the member itself with /v1/members/{memberToken} afterwards.

This endpoint must be enabled on your key. It is off by default and is granted separately from permission to read personal details; a key without it gets 403 lookup_not_enabled.

It only works when your bot lives on the same LINE Official Account / provider the brand uses for membership sign-up. A LINE user id is issued per provider, so an id obtained from a different bot will never match, even for a customer who is a member.

A member who signed up before the brand moved to another LINE channel is still found: the search runs on the stored ids, not on how the record happens to be named. If the same person holds more than one membership record, the most recently created one is returned.

Nothing is found returns 404 not_found without saying why — an unknown person and a person who is not a member of this provider answer identically, on purpose, so this endpoint cannot be used to test which LINE users belong to which brand.

providerId
required
string

Membership provider to search. Must be the providerId of a shop returned by /v1/shops.

lineUserId
required
string
/^U[0-9a-f]{32}$/

The LINE user id of the customer — the letter U followed by 32 lowercase hex characters.

OK

Media typeapplication/json
object
ok
required
boolean
data
required
object
memberId
required

The opaque member token (prefix m1_). Pass it to /v1/members/{memberToken}.

string
serverTime
required

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

string format: date-time
Example
{
"ok": true,
"data": {
"memberId": "m1_Zm9vYmFyYmF6"
},
"serverTime": "2026-09-18T06: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.

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

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

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"
}

The requested scope is not yours. error is one of: shop_not_in_scope (the requested shopId is outside this key’s scope), provider_not_in_scope (the requested providerId is outside this key’s scope), lookup_not_enabled (this key is not allowed to look members up by LINE user id; that permission is granted per key and is off by default).

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": "shop_not_in_scope",
"message": "this API key cannot access that shopId"
}

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.

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

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"
}