borkoldocs

Customers

Customer accounts

How storefront customer accounts, session tokens, and the guest lifecycle work on the Customers module.

View as Markdown

The 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:

  1. 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 the customers:write scope for all POST, PATCH, and DELETE endpoints and the customers:read scope for the GET endpoints. A missing scope returns 403 {"message":"Invalid ability provided."}.
  2. Customer session token (required on customer-authenticated endpoints). POST /v1/api/customers/register and POST /v1/api/customers/login return a token string. Send it on subsequent requests in the X-Customer-Token header. A missing or invalid customer token returns 401 {"message":"Invalid customer token."}.

The two headers are independent and both required on customer-authenticated routes:

text
Authorization: Bearer {api_key}
X-Customer-Token: {token}

Which endpoints need which:

EndpointAPI key scopeX-Customer-Token
POST /registercustomers:writeNo
POST /logincustomers:writeNo
POST /forgot-passwordcustomers:writeNo
POST /reset-passwordcustomers:writeNo
POST /logoutcustomers:writeYes
GET /mecustomers:readYes
PATCH /mecustomers:writeYes
GET /me/addressescustomers:readYes
POST /me/addressescustomers:writeYes
PATCH /me/addresses/{id}customers:writeYes
DELETE /me/addresses/{id}customers:writeYes

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

FieldTypeDescription
idstring (uuid)Customer id. Stable across the guest-to-account upgrade.
namestringFull name.
emailstringEmail address, always lowercase. Unique per store.
phonestring or nullPhone number.
created_atstring (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 with 422 "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_at if 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:

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