Customer accounts & sign-in
litecommerce gives each tenant's shoppers a passwordless customer account: they sign in with a magic link or a one-time code (no password to store or reset), and the session unlocks their profile, saved addresses, order history, and returns. The same API backs both hosted litecheckout and a BYO storefront.
Customer accounts are separate from merchant accounts. A merchant user (Owner/Admin/Staff) signs in to the admin with a Supabase JWT; a customer signs in to the storefront/checkout surface with a customer session. They are different identities, different tokens, and different endpoints.
All paths below are under /api/v1. Every call carries x-organization-slug —
customer identities are tenant-scoped, so a session minted for one tenant is
meaningless to another.
The session credential
A customer session is an opaque token, hashed at rest (ADR-007) — the raw
value exists only in flight, never in a database row. verify issues it two
ways at once so either surface can use it:
- Cookie — an httpOnly, Secure,
SameSite=Lax__Host-cookie that the API sets for the hosted litecheckout surface. The browser sends it automatically and JavaScript can't read it. Because it's a__Host-cookie it is origin-bound — a BYO frontend on a different origin can't use it and should rely on the bearer token instead. - Bearer token — the same session returned in the response body, for a BYO
frontend that holds the token server-side and sends
Authorization: Bearer <token>itself.
Use the cookie on hosted litecheckout; use the bearer for a BYO backend that proxies customer calls. Either way, keep the token server-side — never expose it to client JavaScript.
Sign-in flow (passwordless)
1. Request a challenge
Two issuers, both anonymous (no session yet), both under
/api/v1/public/customer/auth:
POST /public/customer/auth/request-link— sends a one-click magic-link email, with a fallback OTP.POST /public/customer/auth/request-otp— sends a short-lived numeric one-time code.
curl -X POST https://api.litecommerce.io/api/v1/public/customer/auth/request-link \
-H "x-organization-slug: your-store" \
-H "Content-Type: application/json" \
-d '{ "email": "shopper@example.com" }'Both always return a neutral 200 regardless of whether the email belongs
to a customer of this tenant. This is deliberate: the response can't be used to
enumerate which emails have accounts. Don't branch your UI on it — show
"check your email" either way.
2. Verify and start a session
POST /public/customer/auth/verify accepts either shape:
{ "token": "<magic-link-token>" }— from the emailed link, or{ "email": "...", "code": "<otp>" }— from the one-time code.
curl -X POST https://api.litecommerce.io/api/v1/public/customer/auth/verify \
-H "x-organization-slug: your-store" \
-H "Content-Type: application/json" \
-d '{ "email": "shopper@example.com", "code": "123456" }'On success it mints a fresh session: sets the __Host- cookie (hosted) and
returns the bearer token + expiresAt + customerId in the body (BYO). A bad
or expired token/code is a neutral failure — the issue-and-verify surface is
rate-limited per tenant so the OTP can't be brute-forced.
3. Use the session
Authenticated calls live under /api/v1/customer/* and require the session
(cookie or bearer) plus x-organization-slug:
curl https://api.litecommerce.io/api/v1/customer/account/profile \
-H "x-organization-slug: your-store" \
-H "Authorization: Bearer <session-token>"POST /customer/auth/logout ends the session (clears the cookie / revokes the
token).
The account API
All authenticated, all tenant-scoped to the signed-in customer — a session can only ever read or write its own data.
Profile
GET /customer/account/profile— read name, email, phone, email-verified state.PATCH /customer/account/profile— update the customer's own profile fields.
Addresses
A customer keeps an address book with default shipping/billing flags.
GET /customer/account/addresses— list saved addresses.GET /customer/account/addresses/:id— read one.POST /customer/account/addresses— add an address.PATCH /customer/account/addresses/:id— update an address.DELETE /customer/account/addresses/:id— remove an address.
Order history
GET /customer/account/orders— paginated list of the customer's orders.GET /customer/account/orders/:orderNumber— one order's detail (items, totals, status).
Orders are matched to the customer, so the list only ever contains their own orders — there's no cross-customer lookup on this surface.
Returns
GET /customer/account/orders/:orderNumber/returns— return activity for an order.POST /customer/account/orders/:orderNumber/return-actions— start a return for eligible items.
Tokenized actions (no sign-in required)
Some customer actions are reached from an emailed link rather than a signed-in
session — the link carries an opaque, single-purpose token (the a token
family, ADR-007: hashed at rest, expiring, tenant-scoped). This is how, for
example, a "start your return" email works without forcing a login.
GET /customer/actions/:token— preview what the token will do (the action + its context). A safe read, no mutation.POST /customer/actions/:token— consume the token and perform the action, once.
Unknown, expired, revoked, and cross-tenant tokens all return the same neutral
404 — the endpoint never reveals whether a token is real.
The read-only order-status link (the o token, sent with the
order-confirmation email) follows the same hash-at-rest pattern but is a pure
read — see Order status links.
Hosted vs BYO
- Hosted litecheckout renders all of the above for you at
{tenant}.litecheckout.io/account— the customer signs in and manages their account with no integration work. See Hosted litecheckout. - BYO storefront calls these endpoints directly. Proxy them through your own
backend so the session token (bearer) stays server-side; never call
/customer/*from client JavaScript with the raw token.
Related
- Hosted litecheckout — the litecommerce-hosted account + checkout surface
- Order status links — the post-purchase
o-token status page - BYO checkout API — the checkout-session contract a custom storefront drives
- Auth & roles — how merchant auth differs from customer sessions