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

# Admin SDK authentication with API keys and JWT cookies

> Authenticate @spree/admin-sdk with a secret API key for server-to-server, scope-based access or JWT cookie auth for role-based browser admin apps.

The Admin API supports two authentication methods. Both reach the same endpoints — they differ in **where the credential lives** and **how authorization is decided**:

|                   | Secret API key                                                                                             | JWT + cookie                                       |
| ----------------- | ---------------------------------------------------------------------------------------------------------- | -------------------------------------------------- |
| **For**           | Backend integrations, automations, cron jobs                                                               | Browser-based admin apps where a human signs in    |
| **Credential**    | `sk_…` key, created in the admin                                                                           | Short-lived access token + httpOnly refresh cookie |
| **Authorization** | [Scopes](/api-reference/admin-api/authentication) attached to the key (`read_products`, `write_orders`, …) | The admin user's roles and permissions             |
| **Where it runs** | Server-side only — never in a browser                                                                      | Browser (the refresh token never touches your JS)  |

## Secret API key (server-to-server)

Create a secret key in the admin under **Settings → API Keys**, grant it the scopes your integration needs, and pass it to the client:

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

const client = createAdminClient({
  baseUrl: 'https://your-store.com',
  secretKey: process.env.SPREE_SECRET_KEY, // sk_xxx
})
```

Each key carries the list of scopes granted at creation time. A request to an endpoint the key isn't scoped for fails with `code: 'access_denied'` — the error's `details.required_scope` names the missing scope (see [Querying & Errors](/developer/sdk/admin/querying-and-errors#scope-errors)).

<Warning>
  Secret keys grant back-office access to your store. Never embed them in client-side code, mobile apps, or public repositories — keep them in server-side environment variables or a secrets manager.
</Warning>

## JWT + cookie authentication (browser apps)

For browser-based admin tooling, authenticate as an admin user. The flow is designed so that **no long-lived credential is ever exposed to JavaScript**:

* `auth.login()` returns `{ token, user }` — a short-lived access token you hold in memory.
* The **refresh token never appears in JSON.** The server sets it as an `HttpOnly` cookie scoped to `/api/v3/admin/auth`, and the SDK sends requests with `credentials: 'include'` by default, so the cookie flows automatically.
* `auth.refresh()` takes **no arguments** — it's driven entirely by the cookie, and rotates it on every call.
* `auth.logout()` revokes the refresh token server-side and clears the cookie.

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

// A cookie-auth app can start with no credentials at all
const client = createAdminClient({ baseUrl: 'https://your-store.com' })

// Sign in — the refresh token lands in the httpOnly cookie
const { token, user } = await client.auth.login({
  email: 'admin@example.com',
  password: 'password123',
})
client.setToken(token)

// Auto-recover from expired access tokens
client.onUnauthorized(async () => {
  const { token: fresh } = await client.auth.refresh()
  client.setToken(fresh)
  return true // retry the failed request
})

// Sign out — revokes the refresh token server-side
await client.auth.logout()
```

### Bootstrapping a returning session

Because the refresh cookie outlives the in-memory access token, a returning user can be signed back in without re-entering credentials — call `refresh()` on app load and treat failure as "not signed in":

```typescript theme={"theme":"night-owl"}
try {
  const { token } = await client.auth.refresh()
  client.setToken(token)
} catch {
  // No valid session — show the login form
}
```

### Identity providers

`auth.login()` also accepts third-party identity-provider payloads when the server has a [matching strategy registered](/developer/how-to/custom-api-authentication):

```typescript theme={"theme":"night-owl"}
const { token } = await client.auth.login({ provider: 'auth0', token: idpJwt })
```

### Current user and permissions

After signing in, `client.me.get()` returns the admin user's profile together with their serialized permissions — use it to drive what your UI shows:

```typescript theme={"theme":"night-owl"}
const { user, permissions } = await client.me.get()
```

## Staff invitations

The `auth` resource also exposes the unauthenticated invitation-acceptance flow used when onboarding new staff: `auth.lookupInvitation(id, token)` returns the safe-to-render invitation context (store, role, inviter), and `auth.acceptInvitation(id, token, params)` accepts it — issuing a JWT and refresh cookie identical to `login`. Sending invitations is an authenticated operation on [`client.invitations`](/developer/sdk/admin/resources).

## Custom fetch

Pass a custom `fetch` implementation when you need request interception, proxying, or a non-standard runtime:

```typescript theme={"theme":"night-owl"}
const client = createAdminClient({
  baseUrl: 'https://your-store.com',
  secretKey: process.env.SPREE_SECRET_KEY,
  fetch: customFetchImplementation,
})
```
