Skip to main content
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 keyJWT + cookie
ForBackend integrations, automations, cron jobsBrowser-based admin apps where a human signs in
Credentialsk_… key, created in the adminShort-lived access token + httpOnly refresh cookie
AuthorizationScopes attached to the key (read_products, write_orders, …)The admin user’s roles and permissions
Where it runsServer-side only — never in a browserBrowser (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:
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).
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.
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.
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”:
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:
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:
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.

Custom fetch

Pass a custom fetch implementation when you need request interception, proxying, or a non-standard runtime:
const client = createAdminClient({
  baseUrl: 'https://your-store.com',
  secretKey: process.env.SPREE_SECRET_KEY,
  fetch: customFetchImplementation,
})