Cashier-panel customer lookup: identity + balance + activity + badge
The cashier-panel read model, hit at the start of nearly every order. Resolve a customer by phone (E.164) or provider_customer_id (the POS provider’s customer id) and return a single at-a-glance projection: the identity status, the customer’s name and member-since, both money classes (balance.spendable_minor, the actual cashable balance, and balance.locked_promo_minor, the LOCKED cashback awaiting register-to-claim), lifetime activity (spend, visit count, last visit), and a derived badge (new / regular / vip / lapsed).
All four states resolve to 200, never 404. A not_found (stranger) is a valid business answer that drives the cashier’s ‘register this customer’ call to action; returning 404 would route a normal lookup through the error pipeline and deny the cashier the register affordance. A disabled (operator-shut-off) identity is surfaced as not_found deliberately: no PII, no register CTA. A pending_proof proto-wallet returns unclaimed_cashback (the register-to-claim hook). Tenant isolation is structural: every query is scoped to the enterprise resolved from the verified POS-terminal JWT, never from request input, so the same phone at a different enterprise resolves a different row set and cross-enterprise reads are impossible (they surface as not_found). branch_id is echoed unspoofably from the JWT scope. Read-only and rate-limited. The pending_proof state gates all money side effects: this read surfaces the proto-wallet but never credits.
Authorizations
POS terminal JWT minted by /auth/token; carries integrationId + terminal/cashier claims (server-trusted).
Query Parameters
E.164 phone to resolve the customer (e.g. +97433001122), pre-format to E.164 before calling. At least one of phone / provider_customer_id is required; the typed phone is authoritative and provider_customer_id is the no-phone fallback. The server applies the SAME routine to this query value that the write path applied when storing the stored normalized phone, so the two sides byte-match by construction; an input that fails E.164 normalization resolves to not_found (never an error).
"+97433001122"
The POS provider's own customer id (provider-agnostic), used when no phone is available. Resolved within the calling integration's enterprise only.
Response
The customer projection. ALWAYS 200, inspect data.status (registered | pending_proof | not_found). disabled identities surface as not_found. customer is null when status=not_found.
The standard response envelope that every enveloped endpoint serializes through. ok is a boolean discriminator: when ok is true, the typed result is carried in data; when ok is false, a typed error object is returned instead. The meta object is uniform across the entire API surface.
The uniform response metadata block returned on every response. data_completeness_score is computed per call and reports the completeness of the returned data. decision_trace_id is present on responses that carry decision or insight output and can be used to correlate the response with its reasoning. capabilities is an additive, response-level array of hints advertising features the caller may use, and may be extended over time without notice.
The cashier-panel read model returned by the customer-lookup endpoint. The identity status enum drives the cashier UI, and all four states return HTTP 200, never 404: a stranger is a valid business answer that drives the 'register this customer' affordance rather than an error. The projection carries the customer's identity, both money classes (balance.spendable_minor and balance.locked_promo_minor), lifetime activity, and a derived badge.
The typed error object returned with every non-2xx response. code is a string enum (for example WALLET_PROGRAM_AMBIGUOUS, CREDENTIAL_TYPE_UNSUPPORTED, INSUFFICIENT_FUNDS, IDEMPOTENCY_KEY_REUSED, CURRENCY_NOT_SUPPORTED), never a bare HTTP status number. Inspect code for programmatic branching, not the HTTP status. Some codes echo the valid set in details so clients can present or reconcile the accepted values: for example CREDENTIAL_TYPE_UNSUPPORTED lists the supported credential types, CURRENCY_NOT_SUPPORTED lists the supported ISO-4217 currencies, and WALLET_PROGRAM_AMBIGUOUS lists the candidate wallet_program_id values that matched the request.