# Authentication and tokens

How bearer tokens work: format, scopes, channels, expiry, storage, creation, rotation and revocation.

Section: Commerce · Getting started
Canonical URL: https://docs.omniretailconnect.nl/commerce/getting-started/authentication

## Bearer tokens

Clients authenticate with a standalone bearer token. A token is not a user account: it represents one client, and the audit trail identifies the token only.

- The format is `ocm_` followed by 64 hexadecimal characters (256 bits). In these docs the placeholder is `ocm_<token>`.
- Send it in the `Authorization` header: `Authorization: Bearer ocm_<token>`.
- Only that header is read. A token in the query string is not accepted.
- A missing, malformed, unknown, expired or revoked token, or a token that is not enabled for the interface being called, gets the same `401` answer, so a caller cannot tell the cases apart.
- Send exactly one `Authorization` header with exactly one `Bearer` value. If a header holds several `Bearer` values, the last one is used.
- A token is refused from the exact instant it expires. A token whose stored scopes or channels cannot be read grants nothing and gets the same `401`.
- The same tokens work on the MCP server and on the REST API, as far as their channels allow.

> **Security — Keep tokens out of client-side code:** A token is a secret, equivalent to a password for this data. Store it in a secret manager. Never put it in a repository, a shared chat, browser code or a mobile app. Create one token per client and revoke it if you suspect it was exposed.

## Channels

A token carries a list of channels, the interfaces it may be used on: `mcp` for the [MCP server](https://docs.omniretailconnect.nl/commerce/mcp/overview.md) and `api` for the [REST API](https://docs.omniretailconnect.nl/commerce/api/overview.md).

- The default is `mcp` only. A token created without `--channel` cannot use the API, and neither can any token that existed before channels were introduced.
- A token without the `mcp` channel is refused by the MCP server, and a token without the `api` channel is refused by the REST API. Both get the same `401` as an unknown token, so a caller cannot tell why.
- A token with both channels works on both, with separate rate limit counters.
- Channels cannot be changed after creation: create a new token.

> **Warning — For operators: rolling back:** Before rolling back the channels migration, or deploying older code that does not know channels, revoke every token that holds only the `api` channel: older code ignores channels, so an api-only token would become valid on the MCP server. See [Versioning and changelog](https://docs.omniretailconnect.nl/commerce/getting-started/versioning.md#rolling-back).

## Scopes

*Token scopes*

| Scope | Gives |
| --- | --- |
| `commerce.read` | The fifteen read tools and the REST read endpoints, with customer identity masked and some fields withheld. |
| `commerce.read.pii` | In addition: unmasked customer name and email, street, postcode and shipment tracking codes, and order search by email. |
| `commerce.write.products` | In addition: the write tools and endpoints that change products (content, SEO, web address, variants, properties, labels, price, sale price, price breaks, stock, new products, images, brands and the label set) and put products in or out of a category. |
| `commerce.write.collections` | In addition: the write tools and endpoints that change the category tree and the featured products of a category. Not implied by `commerce.write.products`. |
| `commerce.write.discounts` | In addition: create, change, end and delete discount codes. Also lets the token see the discount codes themselves. |
| `commerce.write.orders` | In addition: confirm an offline payment, cancel an unpaid order, add internal notes and record shipments with tracking. No refunds, no customer details. |
| `commerce.write.content` | In addition: create, change and delete CMS pages with their blocks, images and search-engine fields. |

Every other scope adds to `commerce.read` and does not replace it: a token cannot be created with `commerce.read.pii` or any `commerce.write.*` scope alone. The write scopes are independent: none implies another. In the admin you pick a base level under **Access** and optionally tick **Edit permissions** (*Bewerkrechten*):

- **Read** (*Lezen*) is `commerce.read`.
- **Read incl. customer details** (*Lezen incl. klantgegevens*) is `commerce.read` plus `commerce.read.pii`.
- One checkbox per write scope: **Products** (*Producten*, `commerce.write.products`), **Categories** (*Categorieën*, `commerce.write.collections`), **Discount codes** (*Kortingscodes*, `commerce.write.discounts`), **Orders** (*Bestellingen*, `commerce.write.orders`) and **Pages** (*Pagina's*, `commerce.write.content`). Tokens made earlier with the fixed levels *Lezen + producten bewerken* and *Lezen + producten en categorieën bewerken* keep their scopes and work unchanged.

Give tokens **Read** unless the client has a documented need for customer identity, tracking codes, internal order notes or addresses, or for changing the shop. Customer details and edit permissions can be combined; the form then shows both warnings and the shorter lifetime applies. The exact masking rules are in [Privacy and security](https://docs.omniretailconnect.nl/commerce/mcp/privacy-and-security.md), the full scope table in [Scopes and masking](https://docs.omniretailconnect.nl/commerce/reference/scopes-and-masking.md), and the write tools in the [tool reference](https://docs.omniretailconnect.nl/commerce/mcp/tools.md#write-tools).

## Expiry

Every token expires. The default lifetime is 180 days and the maximum is 365 days; there is no "never expires". Prefer shorter lifetimes for experiments and individual users, for example 30 days.

- A token with customer details (`commerce.read.pii`) lives at most 90 days.
- A token with any edit permission (any `commerce.write.*` scope) lives at most 30 days.

These two caps apply to tokens created in the shop admin.

## How tokens are stored

Only the SHA-256 hash of a token is stored. The plain token is shown once, when it is created, and cannot be recovered. If it is lost, create a new token and revoke the old one.

## Create a token in the shop admin

Shop owners create and revoke tokens themselves in the shop admin; no shell access is needed. Only shop administrators (admin staff accounts) can see and use the page. Other staff cannot.

1. Open the shop admin at `/admin` on your shop's domain. The admin is available in Dutch and English; the Dutch labels are given in parentheses.
2. Go to **Settings** (*Instellingen*) and open **API tokens** (*API-tokens*) in the group **Connections** (*Koppelingen*). The page is at `/admin/settings/api-tokens`.
3. Check the **Connection details** (*Verbindingsgegevens*) panel at the top. For **AI agents (MCP)** (*AI-agents (MCP)*) and for the **REST API** it shows whether the feature is **Enabled** (*Ingeschakeld*) or **Not enabled** (*Niet ingeschakeld*) for this shop, the full URL with a copy button, and a link to these docs.
4. Click **New token** (*Nieuw token*).
5. Fill in the form:

   - **Name** (*Naam*): 1 to 120 characters, without control or invisible characters; what or who the token is for, for example "Claude - Anna". It is for your own overview only.
   - **Access** (*Toegang*): one of these levels.

     - **Read** (*Lezen*) shows products, categories, stock, orders, discounts, pages and sales figures, and keeps customer names, email addresses and addresses masked.
     - **Read incl. customer details** (*Lezen incl. klantgegevens*) additionally reveals customers' names, email addresses and street addresses, tracking codes and internal order notes to whoever holds the token. The form shows a warning, because sending them to a third-party AI service has GDPR (AVG) consequences. Choose it only when it is really needed.
   - **Edit permissions** (*Bewerkrechten*), optional: one checkbox per part of the shop the token may change.

     - **Products** (*Producten*): product content, SEO, web address, variants, properties, labels, prices, sale prices, stock and images, adding products, putting products in categories, brands and the label set.
     - **Categories** (*Categorieën*): creating, renaming and moving categories and featuring products in them.
     - **Discount codes** (*Kortingscodes*): creating, changing, ending and deleting discount codes.
     - **Orders** (*Bestellingen*): confirming offline payments, cancelling unpaid orders, internal notes and shipments with track and trace. No refunds.
     - **Pages** (*Pagina's*): creating, changing and deleting CMS pages with their blocks, images and search-engine fields. Never the home page's deletion.

     The group says that a token with edit permissions expires after at most 30 days. Once a box is ticked the form warns that whoever holds the token can change the ticked parts of the shop, and that every change is logged with the token's name. Tick only what the client has to change. The access list can also offer **Order channel (Connect)** (*Orderkanaal (Connect)*) and a store stock level: those are only for OmniRetail's own integrations and are not described here.
   - **Channels** (*Kanalen*): **AI agents (MCP)** (*AI-agents (MCP)*) and/or **REST API**. The token only works on the channels you pick, and you cannot change this later.
   - **Expires after** (*Verloopt na*): 30, 90, 180 (the default) or 365 days. A token with **Read incl. customer details** lives at most 90 days, so the form then only offers 30 and 90 days. A token with any edit permission lives at most 30 days, so the form then only offers 30 days.
   - **Your password** (*Je wachtwoord*): the password you sign in with, to confirm that it is you. The field is shown when you have not confirmed your password in the admin recently (by default in the last three hours). With a wrong password the form says "This password is not correct." (*Dit wachtwoord klopt niet.*). Revoking a token does not ask for a password.
6. Click **Create token** (*Token aanmaken*).
7. Copy the token with the copy button. It is shown **once**, in your browser only, until the window is closed, and cannot be recovered. If it is lost, create a new token and revoke the old one.

Hand the token to the person or system that needs it through a secret manager, not by chat or email. Then follow [Connect Claude Code](https://docs.omniretailconnect.nl/commerce/mcp/connect-claude-code.md), [Connect other clients](https://docs.omniretailconnect.nl/commerce/mcp/connect-other-clients.md) or the [REST API overview](https://docs.omniretailconnect.nl/commerce/api/overview.md).

> **Note — MCP and the REST API are switched on per shop:** OmniRetail switches the MCP server and the REST API on per shop; the shop owner does not. You can create tokens before that, but until the feature is enabled it does not exist: While a feature is switched off, `GET` answers the shop's normal `404` and `POST`, `PUT`, `PATCH` and `DELETE` answer `405` (`Allow: GET, HEAD`), as for any unknown path. The same holds for any path under its URL. The **Connection details** panel shows the current state. Contact your OmniRetail contact to have it enabled.

## Safeguards

- **Password confirmation.** Creating a token needs a recent confirmation of the administrator's own password. Nobody can issue a write token without it.
- **At most 25 active tokens.** Tokens that are not revoked and not expired count. Beyond that the form says "There are already 25 active tokens. Revoke a token you no longer use first." (*Er zijn al 25 actieve tokens. Trek eerst een token in dat u niet meer gebruikt.*). OmniRetail can change this limit for a shop.
- **Customer-data tokens expire sooner.** A token with **Read incl. customer details** lives at most 90 days. Ordinary **Read** tokens can use 30, 90, 180 (the default) or 365 days.
- **Write tokens expire soonest.** A token with any edit permission lives at most 30 days: "A token with edit permissions expires after at most 30 days." (*Een token met bewerkrechten verloopt na hooguit 30 dagen.*).
- **Name rules.** 1 to 120 characters. A name with control or invisible characters is refused: "The name must not contain control or invisible characters." (*De naam mag geen besturings- of onzichtbare tekens bevatten.*).
- **Email notification.** When a token is created, every shop administrator receives an email, "A new API token was created" (*Er is een nieuw API-token aangemaakt*), with who created it, the token name, access level, channels and expiry. The token itself is never included. If you do not recognise it, revoke the token right away and check the administrators of your shop.
- **Activity log.** Creating and revoking are recorded with who and when, never the secret.

## The token list

Below the connection details the table lists every token with its **Name** (*Naam*), **Channels** (*Kanalen*), **Access** (*Toegang*), **Created by** (*Aangemaakt door*; the administrator's name, **Command line** / *Opdrachtregel*, or **Removed user** / *Verwijderde gebruiker*), **Created** (*Aangemaakt*), **Expires** (*Verloopt*), **Last used** (*Laatst gebruikt*; **Never** / *Nog nooit* until the token is first used) and a **Status** badge: **Active** (*Actief*), **Expired** (*Verlopen*) or **Revoked** (*Ingetrokken*). The filter **Created by me** (*Door mij aangemaakt*) shows only your own tokens. The secret is never shown again. "Last used" is updated at most once per minute per token.

The row action **Change history** (*Wijzigingsgeschiedenis*) lists what a token changed through the write tools and endpoints: when, the tool, the channel, the subject and the old and new values. Changes are kept for 730 days.

## Revoking a token

Use the row action **Revoke** (*Intrekken*) and confirm. Integrations that use the token stop working immediately, and this cannot be undone. Tokens cannot be edited or deleted: revoked and expired tokens stay visible in the list.

Creating and revoking tokens is recorded in the shop's activity log with who did it and when. The secret is never recorded. The log descriptions are written in Dutch (for example "API-token aangemaakt: ...").

> **Note — For operators: command line:** The commands below do the same as the admin screen. They exist for operators and scripted setups, and are not needed by shop owners. The command line is an operator tool: it applies none of the admin safeguards (no password prompt, no cap of 25 active tokens, no 90-day limit for customer-data tokens and no 30-day limit for write tokens); only the name rule applies to both. Issue write tokens in the admin.
>
> **Create a token (operators)**
>
> ```bash
> # MCP only (the default)
> php artisan mcp:token:create "Claude Code - Anna" --scope=commerce.read --days=90
>
> # REST API only
> php artisan mcp:token:create "Order desk integration" --scope=commerce.read --channel=api --days=90
>
> # One token for both interfaces
> php artisan mcp:token:create "Analytics" --scope=commerce.read --channel=mcp --channel=api --days=90
>
> # List and revoke
> php artisan mcp:token:list
> php artisan mcp:token:revoke 3
> ```
>
> Options of `mcp:token:create`: `--scope=` (repeatable: `commerce.read`, the default, and/or `commerce.read.pii`, `commerce.write.products`, `commerce.write.collections`, `commerce.write.discounts`, `commerce.write.orders` and `commerce.write.content`, which each need `commerce.read` as well), `--channel=` (repeatable: `mcp`, the default, and/or `api`) and `--days=` (1 to 365, default 180).

## Rotating a token

There is no automatic rotation. Rotate before a token expires, and whenever it may have been exposed.

1. Create a new token in the shop admin for the same client with the same access level and channels, for example named "Claude Code - Anna (rotated)".
2. Hand the new token to the client owner through a secret manager, not by chat or email.
3. Switch the client to the new token and confirm it works, for example with the [MCP smoke test](https://docs.omniretailconnect.nl/commerce/mcp/connect-other-clients.md#smoke-test) or a [REST API request](https://docs.omniretailconnect.nl/commerce/api/overview.md#authentication).
4. Check the token list: the old token's **Last used** should stop advancing.
5. Revoke the old token with **Revoke** (*Intrekken*).
