> ## Documentation Index
> Fetch the complete documentation index at: https://spreecommerce.org/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Authentication

> How to authenticate requests to the Admin API

The Admin API supports two authentication methods: **secret API keys** for server-to-server integrations, and **JWT bearer tokens** for admin SPA / interactive sessions. Every request must include credentials — there is no public surface.

<Warning>
  Secret API keys grant full administrative access to your store. **Never embed them in client-side code, mobile apps, or public repositories.** Use them only from secure server environments.
</Warning>

## Secret API key

Pass the key via the `X-Spree-Api-Key` header:

<CodeGroup>
  ```typescript SDK theme={"theme":"night-owl"}
  import { createAdminClient } from '@spree/admin-sdk'

  const client = createAdminClient({
    baseUrl: 'https://store.example.com',
    secretKey: 'sk_xxx',
  })

  // The SDK automatically sends the secret key with every request
  const { data: products } = await client.products.list()
  ```

  ```bash cURL theme={"theme":"night-owl"}
  curl -X GET 'https://store.example.com/api/v3/admin/orders' \
    -H 'X-Spree-Api-Key: sk_xxx'
  ```
</CodeGroup>

Secret API keys are prefixed with `sk_`. Create them in the Spree admin under **Settings → API Keys** or via the Spree CLI:

```bash theme={"theme":"night-owl"}
spree api-key create --type secret    # Create a new secret key
spree api-key list                     # List existing keys
spree api-key revoke <key_id>          # Revoke a key
```

<Warning>
  If you omit the API key, the API returns `401 Unauthorized`:

  ```json theme={"theme":"night-owl"}
  {
    "error": {
      "code": "authentication_required",
      "message": "Authentication required"
    }
  }
  ```
</Warning>

## JWT bearer token (admin user)

For interactive admin sessions (the Spree admin SPA, custom dashboards, etc.) authenticate as an admin user and use the returned JWT token for subsequent requests.

### Login

<CodeGroup>
  ```typescript SDK theme={"theme":"night-owl"}
  const { token, user } = await client.auth.login({
    email: 'admin@example.com',
    password: 'secret123',
  })

  // Reuse the JWT for subsequent requests. setToken is sticky — every
  // later call on `client` carries the bearer header automatically.
  client.setToken(token)
  const orders = await client.orders.list()
  ```

  ```bash cURL theme={"theme":"night-owl"}
  curl -X POST 'https://store.example.com/api/v3/admin/auth/login' \
    -H 'X-Spree-Api-Key: sk_xxx' \
    -H 'Content-Type: application/json' \
    -d '{"email": "admin@example.com", "password": "secret123"}'
  ```
</CodeGroup>

### Token refresh

JWT tokens expire after 1 hour by default. Refresh them with the current token:

<CodeGroup>
  ```typescript SDK theme={"theme":"night-owl"}
  const { token } = await client.auth.refresh({ token: currentToken })
  ```

  ```bash cURL theme={"theme":"night-owl"}
  curl -X POST 'https://store.example.com/api/v3/admin/auth/refresh' \
    -H 'X-Spree-Api-Key: sk_xxx' \
    -H 'Authorization: Bearer <current_jwt_token>'
  ```
</CodeGroup>

## Permissions

Authorization works differently depending on which credentials you use.

### Secret API keys: scopes

Each secret API key carries a list of **scopes** that grant access to specific resources. Scopes follow a `read_<resource>` / `write_<resource>` convention; `write_<resource>` implies `read_<resource>`.

| Scope                                        | Endpoints                                                                 |
| -------------------------------------------- | ------------------------------------------------------------------------- |
| `read_orders` / `write_orders`               | `/orders/*`, `/orders/:id/items`                                          |
| `read_products` / `write_products`           | `/products/*`, `/variants/*`, `/option_types/*`, `/media/*`               |
| `read_customers` / `write_customers`         | `/customers/*`, `/customers/:id/addresses`, `/customers/:id/credit_cards` |
| `read_payments` / `write_payments`           | `/orders/:id/payments`                                                    |
| `read_fulfillments` / `write_fulfillments`   | `/orders/:id/fulfillments`                                                |
| `read_refunds` / `write_refunds`             | `/orders/:id/refunds`                                                     |
| `read_gift_cards` / `write_gift_cards`       | `/orders/:id/gift_cards`                                                  |
| `read_store_credits` / `write_store_credits` | `/customers/:id/store_credits`, `/orders/:id/store_credits`               |
| `read_categories` / `write_categories`       | `/categories/*`                                                           |
| `read_settings` / `write_settings`           | `/payment_methods`, `/markets`, `/countries`, `/tax_categories`, `/store` |
| `read_dashboard`                             | `/dashboard/*` (analytics)                                                |

Two convenience aliases:

* **`read_all`** — every `read_*` scope
* **`write_all`** — every `read_*` and `write_*` scope (full admin)

If the key lacks the required scope, the API returns `403 Forbidden`:

```json theme={"theme":"night-owl"}
{
  "error": {
    "code": "access_denied",
    "message": "API key lacks scope: write_orders",
    "details": {
      "required_scope": "write_orders"
    }
  }
}
```

The `details.required_scope` field tells you exactly which scope to add.

Pick scopes when creating the key in **Settings → API Keys**. Choose the narrowest set that covers your integration's needs.

### JWT bearer tokens: CanCanCan abilities

JWT-authenticated admin users are authorized via [CanCanCan](https://github.com/CanCanCommunity/cancancan) abilities derived from their `Spree::Role`s. The SPA uses this fine-grained model to render UI conditionally; partial-permission staff users see only the resources their role grants.

If the caller lacks permission for a specific action, the API returns `403 Forbidden`:

```json theme={"theme":"night-owl"}
{
  "error": {
    "code": "access_denied",
    "message": "You are not authorized to perform this action"
  }
}
```

## Authentication summary

| Method         | Header                          | Use case                        | Authorization       |
| -------------- | ------------------------------- | ------------------------------- | ------------------- |
| Secret API key | `X-Spree-Api-Key: sk_xxx`       | Server-to-server integrations   | Scopes              |
| JWT token      | `Authorization: Bearer <token>` | Interactive admin sessions; SPA | CanCanCan abilities |

<Note>
  If both headers are present, the JWT token wins: CanCanCan applies and scopes are ignored. This lets you use `sk_xxx` to bootstrap a session and then issue per-user JWTs for individual admin actions.
</Note>
