Customers
Customer accounts
How storefront customer accounts, session tokens, and the guest lifecycle work on the Customers module.
View as MarkdownThe Customers module gives your storefront customer accounts: registration, login, password reset, a profile, and saved addresses. All routes live under /v1/api/customers and require the customers module to be enabled for your store; calling them without it returns 403 {"error": "Module not enabled: customers"}.
Two tokens, two headers
Customer endpoints use a layered authentication model:
- Tenant API key (always required). Every request carries your store's API key as
Authorization: Bearer {api_key}, exactly like the rest of the storefront API. The key needs thecustomers:writescope for all POST, PATCH, and DELETE endpoints and thecustomers:readscope for the GET endpoints. A missing scope returns403 {"message":"Invalid ability provided."}. - Customer session token (required on customer-authenticated endpoints).
POST /v1/api/customers/registerandPOST /v1/api/customers/loginreturn atokenstring. Send it on subsequent requests in theX-Customer-Tokenheader. A missing or invalid customer token returns401 {"message":"Invalid customer token."}.
The two headers are independent and both required on customer-authenticated routes:
Authorization: Bearer {api_key}
X-Customer-Token: {token}Which endpoints need which:
| Endpoint | API key scope | X-Customer-Token |
|---|---|---|
POST /register | customers:write | No |
POST /login | customers:write | No |
POST /forgot-password | customers:write | No |
POST /reset-password | customers:write | No |
POST /logout | customers:write | Yes |
GET /me | customers:read | Yes |
PATCH /me | customers:write | Yes |
GET /me/addresses | customers:read | Yes |
POST /me/addresses | customers:write | Yes |
PATCH /me/addresses/{id} | customers:write | Yes |
DELETE /me/addresses/{id} | customers:write | Yes |
Customer session tokens do not expire on a timer. They are revoked by POST /logout (one token) and by a successful password reset (all of the customer's tokens).
The public customer object
Customer-facing endpoints return this shape (internal fields such as is_guest, type, and customer_group_id are never exposed on the storefront API):
| Field | Type | Description |
|---|---|---|
id | string (uuid) | Customer id. Stable across the guest-to-account upgrade. |
name | string | Full name. |
email | string | Email address, always lowercase. Unique per store. |
phone | string or null | Phone number. |
created_at | string (ISO 8601) | Creation timestamp, for example 2026-08-14T10:32:11+00:00. |
Guest lifecycle
Cart email capture in the Commerce module can create a customer row with only an email and no password (a guest). This matters in three places:
- Registration upgrades guests. Registering with an email that belongs to a guest row upgrades that row in place: same
id, order history preserved. Only an email that belongs to a real (non-guest) account fails with422 "Email already registered.". - Guests cannot log in. They have no password, so login fails with the generic
422 "Invalid credentials.". - Password reset also upgrades. Completing a reset sets a password, marks the account non-guest, and sets
email_verified_atif it was unset. This is a second path from guest checkout to a full account.
Rate limits
All customer endpoints share the storefront limit of 120 requests per minute per store (429 with a Retry-After header when exceeded). The four credential endpoints (register, login, forgot-password, reset-password) carry an additional limit of 10 requests per minute per client IP to slow brute force attempts.
Errors
All error envelopes are the standard storefront shapes:
| Status | Body | Cause |
|---|---|---|
| 401 | {"message":"Unauthenticated."} | Missing or invalid tenant API key. |
| 401 | {"message":"Invalid customer token."} | Missing or invalid X-Customer-Token on a customer-authenticated route. |
| 403 | {"message":"Invalid ability provided."} | API key lacks the required scope. |
| 403 | {"error":"Module not enabled: customers"} | Customers module disabled for the store. Note the error key. |
| 403 | {"message":"Tenant suspended.","code":"tenant_suspended"} | Store suspended. |
| 404 | {"message":"..."} | Resource not found (or not owned by the authenticated customer). |
| 422 | {"message":"...","errors":{"field":["..."]}} | Validation failure. |
| 429 | {"message":"Too Many Attempts."} | Rate limit exceeded. |