# Customer accounts

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:

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

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