# Authentication

URL: /docs/authentication

The store key, the server secret, registered websites, bearer tokens for customers and merchant integrations, and how to rotate credentials.

Every call names its store with a public key. Calls from your servers also prove where they come from with the store's server secret, and calls from a browser are accepted only from websites you have registered. Customer and merchant routes add a bearer token on top.

## The headers [#the-headers]

| Header | When | Example |
|---|---|---|
| `X-Tenant-Key` | Every call. It is the only thing that identifies the store. | `pk_live_…` |
| `X-Tenant-Secret` | Every call from a server. Never from a browser. | your server secret |
| `Authorization` | Customer routes (a token from `POST /v1/auth/login`) and Merchant API routes (a merchant API key). | `Bearer …` |
| `X-Client-IP` | Optional on server calls: the shopper's IP address, so rate limits apply per shopper. Trusted only with a valid secret. | `203.0.113.7` |
| `X-Cart-Token` | Cart and checkout routes. | from `POST /v1/cart` |
| `X-Order-Access-Token` | Viewing a guest's order. | from checkout |
| `Idempotency-Key` | Writes you might retry. See [Idempotency keys](/docs/concepts/idempotency). | a UUID |

All of these are in **Settings → Storefront API** in your store admin, apart from customer tokens (which the API issues) and merchant API keys.

## Server calls and browser calls [#server-calls-and-browser-calls]

The API tells the two apart by the `Origin` header, which browsers add and servers do not.

| | Server call | Browser call |
|---|---|---|
| Made by | Your storefront server, a script, an integration | JavaScript on a web page |
| Must carry | `X-Tenant-Key` and `X-Tenant-Secret` | `X-Tenant-Key` only, from a registered website |
| Can use | Every route its credentials allow, and `X-Client-IP` | The public routes. Routes that need the secret, such as consent, refuse it. |

We recommend calling the API only from your own server, as a backend-for-frontend. The [starters](/docs/starters) all work this way, and refuse to start in production without the secret.

```ts title="lib/elaraone.ts (server only)"
const API = 'https://api.elaraone.app/api/v1'

export async function elaraone(path: string, init: RequestInit = {}, shopperIp?: string) {
  return fetch(`${API}${path}`, {
    ...init,
    headers: {
      'X-Tenant-Key': process.env.ELARAONE_TENANT_KEY!,
      'X-Tenant-Secret': process.env.ELARAONE_TENANT_SECRET!,
      ...(shopperIp ? { 'X-Client-IP': shopperIp } : {}),
      ...init.headers,
    },
  })
}
```

<Callout type="warn" title="Never forward the browser's headers">
  Server-side `fetch` in Node.js sends no `Origin`. If you copy the shopper's request headers onto your API call, their `Origin` comes too and turns a server call into a browser call. Build the headers yourself, as above.
</Callout>

Keep the secret out of any environment variable your framework copies into browser code: `NEXT_PUBLIC_*` in Next.js, `NUXT_PUBLIC_*` and `VITE_*` in Nuxt, `PUBLIC_*` in Astro.

## Registered websites [#registered-websites]

A browser call is accepted only from a website the store has registered in **Settings → Storefront API → Your websites**. Enter each origin with its scheme and host, such as `https://shop.example.com`, and no path. Only `https://` websites can be registered; register `www.` and any separate checkout host as well.

If your storefront never calls the API from a browser, switch **Browser calls** off on the same page. The store then refuses every call without the secret.

CORS is set per store. A response to an accepted browser call echoes your website in `Access-Control-Allow-Origin`, never `*`, and credentials (cookies) are never allowed.

<Callout type="info" title="The website check is not a password">
  It stops other websites' JavaScript from using your public key. A program that is not a browser can send any `Origin` it likes, so browser calls only ever get the public tier. Anything private needs the secret, and so a server.
</Callout>

## The API lock [#the-api-lock]

A new store starts in **report-only** for 14 days: nothing is refused, but every call the lock would refuse is counted and shown in **Settings → Storefront API → API lock**, by website and by kind. After 14 days the lock turns on by itself. You can turn it on sooner from the same page.

Once the lock is on, these calls are refused with `403`:

| `error.code` | Why | Fix |
|---|---|---|
| `TENANT_SECRET_REQUIRED` | A server call without `X-Tenant-Secret`. | Send the secret on every server call. |
| `TENANT_SECRET_INVALID` | A server call with the wrong secret, or a secret-only route called without it. | Copy the current secret from Settings → Storefront API. |
| `ORIGIN_NOT_ALLOWED` | A browser call from a website the store has not registered. | Register the website, or call from your server. |
| `BROWSER_CALLS_DISABLED` | A browser call while browser calls are switched off. | Call from your server with the secret. |

None of these is worth retrying: the same call gets the same answer until your code or the store's settings change.

**Before the lock turns on**, check that every website is registered, that every server, job and integration sends the secret, and that the report shows no calls without the secret for the last few days.

## Customer tokens [#customer-tokens]

`POST /v1/auth/login` (or `POST /v1/auth/register`) returns a bearer token for the customer. Send it as `Authorization: Bearer …`, together with your store key and secret, on account routes such as `GET /v1/auth/me` and `GET /v1/orders`. Keep the token on your server, for example in an httpOnly cookie on your own domain. See [Customers and accounts](/docs/concepts/customers).

## Merchant API keys [#merchant-api-keys]

The Merchant API (`/v1/merchant/…`) is for back-office integrations: CRM sync, order fulfilment, forms, flows and pages. A merchant API key is sent as a bearer token, alongside the store key and secret, and carries only the abilities it was given:

| Ability | Allows |
|---|---|
| `crm:read`, `crm:write` | Reading and writing contacts, companies, tasks, notes and associations |
| `read_orders`, `manage_orders` | Listing orders; dispatching, marking delivered and recording offline refunds |
| `manage_forms`, `manage_flows`, `manage_pages` | Forms, automation flows and CMS pages |
| `manage_consent` | The cookie consent banner and consent records |
| `view_conversations` | Inbox conversations |

A key without the ability a route needs gets `403 FORBIDDEN`. A missing or revoked key gets `401 UNAUTHENTICATED`.

## Rotating credentials [#rotating-credentials]

**The store key.** Settings → Storefront API → **Rotate store key**. The new key keeps the old prefix. The old key keeps working for 7 days, and responses to calls made with it carry `X-Tenant-Key-Deprecated: true`: log that header, so a forgotten deployment shows up. **End overlap now** stops the old key at once, for a key that has leaked. After the overlap, the old key gets `404 TENANT_NOT_FOUND`.

**The server secret.** Rotate it when it may have leaked: it was seen in a browser, committed to a repository, or someone who knew it has left. The change is immediate, with no overlap, so update every server's environment and restart. To keep a locked shop working throughout, rotate just before you deploy.

Rotating either asks for your admin password and authenticator code, and is recorded in the activity log.
