Spree Admin API
Back-office REST API for managing products, variants, inventory, promotions, orders, users, and store configuration. Documented with OpenAPI 3.0.
Back-office REST API for managing products, variants, inventory, promotions, orders, users, and store configuration. Documented with OpenAPI 3.0.
---
openapi: 3.0.3
info:
title: Admin API
contact:
name: Spree Commerce
url: https://spreecommerce.org
email: [email protected]
description: |
Spree Admin API v3 - Administrative API for managing products, orders, and store settings.
## Authentication
The Admin API requires a secret API key passed in the `x-spree-api-key` header.
Secret API keys can be generated in the Spree admin dashboard.
## Response Format
All responses are JSON. List endpoints return paginated responses with `data` and `meta` keys.
Single resource endpoints return a flat JSON object.
## Resource IDs
Every resource is identified by an opaque string ID (e.g. `prod_86Rf07xd4z`,
`variant_k5nR8xLq`, `or_UkLWZg9DAJ`). Use these IDs everywhere — URL paths,
request bodies, and Ransack filters all accept them directly.
## Error Handling
Errors return a consistent format:
```json
{
"error": {
"code": "validation_error",
"message": "Validation failed",
"details": { "name": ["can't be blank"] }
}
}
```
version: v3
paths:
"/api/v3/admin/auth/login":
post:
summary: Login
tags:
- Authentication
security:
- api_key: []
description: |
Authenticates an admin user and returns a short-lived JWT access token.
The rotatable refresh token is set in an HttpOnly cookie — it is not
included in the response body.
Dispatches by the `provider` field to a strategy registered in
`Spree.admin_authentication_strategies`. When `provider` is omitted it
defaults to `email`, which uses the built-in email/password strategy.
To plug in a third-party identity provider (Okta, Azure AD, Google
Workspace SSO, a custom JWT issuer, SAML, etc.), register a
`Spree::Authentication::Strategies::BaseStrategy` subclass under a
provider key, then send `{ "provider": "<your_key>", ... }` with the
fields your strategy requires. The endpoint returns the same Spree-issued
JWT regardless of which strategy authenticated the request.
x-codeSamples:
- lang: javascript
label: Spree Admin SDK
source: |-
import { createAdminClient } from '@spree/admin-sdk'
const client = createAdminClient({
baseUrl: 'https://your-store.com',
secretKey: 'sk_xxx',
})
// The refresh token is set as an HttpOnly cookie; only `token` and `user` come back in the body.
const auth = await client.auth.login({
email: '[email protected]',
password: 'password123',
})
parameters:
- name: x-spree-api-key
in: header
required: true
schema:
type: string
responses:
'200':
description: login successful
content:
application/json:
example:
token: eyJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoxLCJ1c2VyX3R5cGUiOiJhZG1pbiIsImp0aSI6ImUyMzYxNTc5LWMxZDMtNDc0Yi1hM2E1LTcwNWMzYTZmMGUxYSIsImlzcyI6InNwcmVlIiwiYXVkIjoiYWRtaW5fYXBpIiwiZXhwIjoxNzgxMjg1MzI3fQ.5GaxxoaCeEEpGgum01ZV0z56ZpHDWaFSyX_KvEob3ws
user:
id: admin_UkLWZg9DAJ
email: [email protected]
first_name: Lisandra
last_name: Dare
full_name: Lisandra Dare
created_at: '2026-06-12T17:23:47.304Z'
updated_at: '2026-06-12T17:23:47.304Z'
roles:
- id: role_UkLWZg9DAJ
name: admin
schema:
"$ref": "#/components/schemas/AuthResponse"
'401':
description: invalid credentials
content:
application/json:
example:
error:
code: authentication_failed
message: Invalid email or password
schema:
"$ref": "#/components/schemas/ErrorResponse"
requestBody:
content:
application/json:
schema:
oneOf:
- title: EmailPasswordLogin
description: Built-in email/password authentication (default when
`provider` is omitted).
type: object
properties:
provider:
type: string
enum:
- email
default: email
email:
type: string
format: email
example: [email protected]
password:
type: string
example: password123
required:
- email
- password
- title: ProviderLogin
description: |
Provider-dispatched login. The `provider` key selects a registered
strategy class; the remaining fields are forwarded to the strategy's
`authenticate` method. Required fields depend on the registered strategy
— consult its documentation.
type: object
properties:
provider:
type: string
example: okta
description: Registered provider key (anything other than `email`).
not:
enum:
- email
required:
- provider
additionalProperties: true
"/api/v3/admin/auth/refresh":
post:
summary: Refresh token
tags:
- Authentication
security:
- api_key: []
description: |
Exchanges the HttpOnly refresh-token cookie for a new access JWT and a
rotated refresh token cookie. No request body or Authorization header
is required — the cookie alone authenticates the call.
x-codeSamples:
- lang: javascript
label: Spree Admin SDK
source: |-
import { createAdminClient } from '@spree/admin-sdk'
const client = createAdminClient({
baseUrl: 'https://your-store.com',
secretKey: 'sk_xxx',
})
// Driven entirely by the HttpOnly refresh-token cookie + CSRF header (set by the SDK).
const auth = await client.auth.refresh()
parameters:
- name: x-spree-api-key
in: header
required: true
schema:
type: string
responses:
'200':
description: refresh successful
content:
application/json:
example:
token: eyJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoxLCJ1c2VyX3R5cGUiOiJhZG1pbiIsImp0aSI6IjYxNTkwZmYyLWEwMTctNDUyZi05ZjQ3LTBhM2JhYWZlYWQ0YiIsImlzcyI6InNwcmVlIiwiYXVkIjoiYWRtaW5fYXBpIiwiZXhwIjoxNzgxMjg1MzI4fQ.l53NCGqm7_mD8L54Wq7AB86HGHqolGJEplh7Dg_QqJw
user:
id: admin_UkLWZg9DAJ
email: [email protected]
first_name: Lavonda
last_name: Bogan
full_name: Lavonda Bogan
created_at: '2026-06-12T17:23:48.682Z'
updated_at: '2026-06-12T17:23:48.682Z'
roles:
- id: role_UkLWZg9DAJ
name: admin
schema:
"$ref": "#/components/schemas/AuthResponse"
'401':
description: missing or invalid refresh-token cookie
content:
application/json:
example:
error:
code: invalid_refresh_token
message: Refresh token cookie missing
schema:
"$ref": "#/components/schemas/ErrorResponse"
"/api/v3/admin/auth/logout":
post:
summary: Logout
tags:
- Authentication
security:
- api_key: []
description: Revokes the refresh-token cookie, effectively logging the admin
out.
x-codeSamples:
- lang: javascript
label: Spree Admin SDK
source: |-
import { createAdminClient } from '@spree/admin-sdk'
const client = createAdminClient({
baseUrl: 'https://your-store.com',
secretKey: 'sk_xxx',
})
await client.auth.logout()
parameters:
- name: x-spree-api-key
in: header
required: true
schema:
type: string
responses:
'204':
description: logout successful
"/api/v3/admin/me":
get:
summary: Get current admin user and permissions
tags:
- Authentication
security:
- api_key: []
bearer_auth: []
description: Returns the current admin user profile and a serialized list of
permissions (CanCanCan rules). The SPA uses these to drive UI permission checks.
x-codeSamples:
- lang: javascript
label: Spree Admin SDK
source: |-
import { createAdminClient } from '@spree/admin-sdk'
const client = createAdminClient({
baseUrl: 'https://your-store.com',
secretKey: 'sk_xxx',
})
const me = await client.me.get()
if (me.permissions.some((r) => r.allow && r.actions.includes('manage') && r.subjects.includes('Spree::Product'))) {
// show "Create product" button
}
parameters:
- name: x-spree-api-key
in: header
required: true
schema:
type: string
- name: Authorization
in: header
required: true
description: Bearer token for admin authentication
schema:
type: string
responses:
'200':
description: current admin user and permissions
content:
application/json:
example:
user:
id: admin_UkLWZg9DAJ
email: [email protected]
first_name: Thelma
last_name: Cronin
full_name: Thelma Cronin
created_at: '2026-06-12T17:24:22.538Z'
updated_at: '2026-06-12T17:24:22.538Z'
roles:
- id: role_UkLWZg9DAJ
name: admin
permissions:
- allow: true
actions:
- manage
subjects:
- all
has_conditions: false
- allow: false
actions:
- cancel
subjects:
- Spree::Order
has_conditions: false
- allow: true
actions:
- cancel
subjects:
- Spree::Order
has_conditions: true
- allow: false
actions:
- destroy
subjects:
- Spree::Order
has_conditions: false
- allow: true
actions:
- destroy
subjects:
- Spree::Order
has_conditions: true
- allow: false
actions:
- edit
- update
subjects:
- Spree::RefundReason
has_conditions: true
- allow: false
actions:
- edit
- update
subjects:
- Spree::ReimbursementType
has_conditions: true
- allow: false
actions:
- update
- destroy
subjects:
- Spree::Role
has_conditions: true
schema:
"$ref": "#/components/schemas/MeResponse"
'401':
description: unauthorized
content:
application/json:
example:
error:
code: authentication_required
message: Authentication required
schema:
"$ref": "#/components/schemas/ErrorResponse"
"/api/v3/admin/allowed_origins":
get:
summary: List allowed origins
tags:
- Allowed Origins
security:
- api_key: []
bearer_auth: []
description: |-
Returns the CORS allowlist for the current store. Each entry is a
bare `scheme://host[:port]` permitted to call the admin API from a
browser. Backs the `Rack::Cors` allowlist and the CSRF boundary of
the admin cookie session (see
`docs/plans/5.5-admin-auth-cookie-refresh.md`).
**Required scope:** `read_settings` (for API-key authentication).
x-codeSamples:
- lang: javascript
label: Spree Admin SDK
source: |-
import { createAdminClient } from '@spree/admin-sdk'
const client = createAdminClient({
baseUrl: 'https://your-store.com',
secretKey: 'sk_xxx',
})
const { data: origins } = await client.allowedOrigins.list()
parameters:
- name: x-spree-api-key
in: header
required: true
schema:
type: string
- name: Authorization
in: header
required: true
description: Bearer token for admin authentication
schema:
type: string
- name: page
in: query
required: false
description: Page number
schema:
type: integer
- name: limit
in: query
required: false
description: Number of records per page
schema:
type: integer
- name: q[origin_cont]
in: query
required: false
description: Filter by origin (contains)
schema:
type: string
- name: sort
in: query
required: false
description: Sort by field. Prefix with `-` for descending (e.g., `-created_at`).
schema:
type: string
- name: fields
in: query
required: false
description: Comma-separated list of fields to include. id is always included.
schema:
type: string
responses:
'200':
description: allowed origins found
content:
application/json:
example:
data:
- id: ao_UkLWZg9DAJ
origin: https://shop.example.com
created_at: '2026-06-12T17:23:42.410Z'
updated_at: '2026-06-12T17:23:42.410Z'
meta:
page: 1
limit: 25
count: 1
pages: 1
from: 1
to: 1
in: 1
previous:
next:
schema:
type: object
properties:
data:
type: array
items:
"$ref": "#/components/schemas/AllowedOrigin"
meta:
"$ref": "#/components/schemas/PaginationMeta"
required:
- data
- meta
'401':
description: unauthorized
content:
application/json:
example:
error:
code: authentication_required
message: Authentication required
schema:
"$ref": "#/components/schemas/ErrorResponse"
post:
summary: Create an allowed origin
tags:
- Allowed Origins
security:
- api_key: []
bearer_auth: []
description: |-
Adds an origin to the admin CORS allowlist. The value must be a bare
`scheme://host[:port]` (no path, query, or fragment) and use `http` or
`https`.
**Required scope:** `write_settings` (for API-key authentication).
x-codeSamples:
- lang: javascript
label: Spree Admin SDK
source: |-
import { createAdminClient } from '@spree/admin-sdk'
const client = createAdminClient({
baseUrl: 'https://your-store.com',
secretKey: 'sk_xxx',
})
const origin = await client.allowedOrigins.create({
origin: 'https://admin.example.com',
})
parameters:
- name: x-spree-api-key
in: header
required: true
schema:
type: string
- name: Authorization
in: header
required: true
description: Bearer token for admin authentication
schema:
type: string
responses:
'201':
description: allowed origin created
content:
application/json:
example:
id: ao_gbHJdmfrXB
origin: https://admin.example.com
created_at: '2026-06-12T17:23:43.075Z'
updated_at: '2026-06-12T17:23:43.075Z'
schema:
"$ref": "#/components/schemas/AllowedOrigin"
'422':
description: validation error
content:
application/json:
example:
error:
code: validation_error
message: Origin is invalid
details:
origin:
- is invalid
schema:
"$ref": "#/components/schemas/ErrorResponse"
requestBody:
content:
application/json:
schema:
type: object
properties:
origin:
type: string
example: https://admin.example.com
required:
- origin
"/api/v3/admin/allowed_origins/{id}":
parameters:
- name: id
in: path
required: true
description: Allowed origin ID
schema:
type: string
get:
summary: Get an allowed origin
tags:
- Allowed Origins
security:
- api_key: []
bearer_auth: []
description: |-
Returns a single allowed origin by prefixed ID.
**Required scope:** `read_settings` (for API-key authentication).
x-codeSamples:
- lang: javascript
label: Spree Admin SDK
source: |-
import { createAdminClient } from '@spree/admin-sdk'
const client = createAdminClient({
baseUrl: 'https://your-store.com',
secretKey: 'sk_xxx',
})
const origin = await client.allowedOrigins.get('ao_xxx')
parameters:
- name: x-spree-api-key
in: header
required: true
schema:
type: string
- name: Authorization
in: header
required: true
description: Bearer token for admin authentication
schema:
type: string
- name: fields
in: query
required: false
description: Comma-separated list of fields to include. id is always included.
schema:
type: string
responses:
'200':
description: allowed origin found
content:
application/json:
example:
id: ao_UkLWZg9DAJ
origin: https://shop.example.com
created_at: '2026-06-12T17:23:43.396Z'
updated_at: '2026-06-12T17:23:43.396Z'
schema:
"$ref": "#/components/schemas/AllowedOrigin"
'404':
description: allowed origin not found
content:
application/json:
example:
error:
code: record_not_found
message: Allowed origin not found
schema:
"$ref": "#/components/schemas/ErrorResponse"
patch:
summary: Update an allowed origin
tags:
- Allowed Origins
security:
- api_key: []
bearer_auth: []
description: |-
Updates an existing allowed origin.
**Required scope:** `write_settings` (for API-key authentication).
x-codeSamples:
- lang: javascript
label: Spree Admin SDK
source: |-
import { createAdminClient } from '@spree/admin-sdk'
const client = createAdminClient({
baseUrl: 'https://your-store.com',
secretKey: 'sk_xxx',
})
const origin = await client.allowedOrigins.update('ao_xxx', {
origin: 'https://www.example.com',
})
parameters:
- name: x-spree-api-key
in: header
required: true
schema:
type: string
- name: Authorization
in: header
required: true
description: Bearer token for admin authentication
schema:
type: string
responses:
'200':
description: allowed origin updated
content:
application/json:
example:
id: ao_UkLWZg9DAJ
origin: https://www.example.com
created_at: '2026-06-12T17:23:44.064Z'
updated_at: '2026-06-12T17:23:44.366Z'
schema:
"$ref": "#/components/schemas/AllowedOrigin"
'422':
description: validation error
content:
application/json:
example:
error:
code: validation_error
message: Origin must be an origin (scheme and host) without path,
query, or fragment
details:
origin:
- must be an origin (scheme and host) without path, query, or
fragment
schema:
"$ref": "#/components/schemas/ErrorResponse"
requestBody:
content:
application/json:
schema:
type: object
properties:
origin:
type: string
delete:
summary: Delete an allowed origin
tags:
- Allowed Origins
security:
- api_key: []
bearer_auth: []
description: |-
Removes an origin from the admin CORS allowlist. After deletion the
admin SPA running at that origin will no longer be able to call the
admin API from a browser.
**Required scope:** `write_settings` (for API-key authentication).
x-codeSamples:
- lang: javascript
label: Spree Admin SDK
source: |-
import { createAdminClient } from '@spree/admin-sdk'
const client = createAdminClient({
baseUrl: 'https://your-store.com',
secretKey: 'sk_xxx',
})
await client.allowedOrigins.delete('ao_xxx')
parameters:
- name: x-spree-api-key
in: header
required: true
schema:
type: string
- name: Authorization
in: header
required: true
description: Bearer token for admin authentication
schema:
type: string
responses:
'204':
description: allowed origin deleted
"/api/v3/admin/api_keys":
get:
summary: List API keys
tags:
- API Keys
security:
- api_key: []
bearer_auth: []
description: |-
Returns publishable and secret API keys for the current store. Secret keys are listed by `token_prefix` only — the plaintext token is delivered exactly once on create.
**Required scope:** `read_api_keys` (for API-key authentication).
x-codeSamples:
- lang: javascript
label: Spree Admin SDK
source: |-
import { createAdminClient } from '@spree/admin-sdk'
const client = createAdminClient({
baseUrl: 'https://your-store.com',
secretKey: 'sk_xxx',
})
const { data: keys } = await client.apiKeys.list()
parameters:
- name: x-spree-api-key
in: header
required: true
schema:
type: string
- name: Authorization
in: header
required: true
schema:
type: string
responses:
'200':
description: API keys found
content:
application/json:
example:
data:
- id: key_UkLWZg9DAJ
name: Storefront key
key_type: publishable
token_prefix:
scopes: []
created_at: '2026-06-12T17:23:45.015Z'
updated_at: '2026-06-12T17:23:45.015Z'
revoked_at:
last_used_at:
plaintext_token: pk_5AskmHCv2omYyoBr3mehREcX
created_by_email:
- id: key_gbHJdmfrXB
name: Backend integration
key_type: secret
token_prefix: sk_qBGxwGooh
scopes:
- write_all
created_at: '2026-06-12T17:23:45.016Z'
updated_at: '2026-06-12T17:23:45.016Z'
revoked_at:
last_used_at:
plaintext_token:
created_by_email:
- id: key_EfhxLZ9ck8
name: minima
key_type: secret
token_prefix: sk_2bVt6n2wq
scopes:
- write_all
created_at: '2026-06-12T17:23:45.017Z'
updated_at: '2026-06-12T17:23:45.017Z'
revoked_at:
last_used_at:
plaintext_token:
created_by_email:
meta:
page: 1
limit: 25
count: 3
pages: 1
from: 1
to: 3
in: 3
previous:
next:
post:
summary: Create an API key
tags:
- API Keys
security:
- api_key: []
bearer_auth: []
description: |-
Creates a publishable or secret API key. The plaintext token is included in the response **once** for secret keys; publishable keys expose their token on every read since they are intended for client-side use.
**Required scope:** `write_api_keys` (for API-key authentication).
x-codeSamples:
- lang: javascript
label: Spree Admin SDK
source: |-
import { createAdminClient } from '@spree/admin-sdk'
const client = createAdminClient({
baseUrl: 'https://your-store.com',
secretKey: 'sk_xxx',
})
const key = await client.apiKeys.create({
name: 'Backend integration',
key_type: 'secret',
scopes: ['read_orders', 'write_orders']
})
// `key.plaintext_token` is available only on this response.
parameters:
- name: x-spree-api-key
in: header
required: true
schema:
type: string
- name: Authorization
in: header
required: true
schema:
type: string
responses:
'201':
description: secret key created — plaintext token returned once
content:
application/json:
example:
id: key_VqXmZF31wY
name: CI key
key_type: secret
token_prefix: sk_vGdftBeFe
scopes:
- read_orders
created_at: '2026-06-12T17:23:45.625Z'
updated_at: '2026-06-12T17:23:45.625Z'
revoked_at:
last_used_at:
plaintext_token: sk_vGdftBeFecVvHg1towF93knP
created_by_email: [email protected]
'422':
description: validation error
content:
application/json:
example:
error:
code: validation_error
message: Name can't be blank and Scopes can't be blank
details:
name:
- can't be blank
scopes:
- can't be blank
requestBody:
content:
application/json:
schema:
type: object
required:
- name
- key_type
properties:
name:
type: string
example: Backend integration
key_type:
type: string
enum:
- publishable
- secret
scopes:
type: array
items:
type: string
example:
- read_orders
- write_orders
"/api/v3/admin/api_keys/{id}":
get:
summary: Show an API key
tags:
- API Keys
security:
- api_key: []
bearer_auth: []
description: "**Required scope:** `read_api_keys` (for API-key authentication)."
x-codeSamples:
- lang: javascript
label: Spree Admin SDK
source: |-
import { createAdminClient } from '@spree/admin-sdk'
const client = createAdminClient({
baseUrl: 'https://your-store.com',
secretKey: 'sk_xxx',
})
const key = await client.apiKeys.get('key_xxx')
parameters:
- name: x-spree-api-key
in: header
required: true
schema:
type: string
- name: Authorization
in: header
required: true
schema:
type: string
- name: id
in: path
required: true
schema:
type: string
responses:
'200':
description: API key found
content:
application/json:
example:
id: key_UkLWZg9DAJ
name: Storefront key
key_type: publishable
token_prefix:
scopes: []
created_at: '2026-06-12T17:23:45.944Z'
updated_at: '2026-06-12T17:23:45.944Z'
revoked_at:
last_used_at:
plaintext_token: pk_bZ5pTDmsuFozzLiikk62bCgQ
created_by_email:
delete:
summary: Delete an API key
tags:
- API Keys
security:
- api_key: []
bearer_auth: []
description: "**Required scope:** `write_api_keys` (for API-key authentication)."
x-codeSamples:
- lang: javascript
label: Spree Admin SDK
source: |-
import { createAdminClient } from '@spree/admin-sdk'
const client = createAdminClient({
baseUrl: 'https://your-store.com',
secretKey: 'sk_xxx',
})
await client.apiKeys.delete('key_xxx')
parameters:
- name: x-spree-api-key
in: header
required: true
schema:
type: string
- name: Authorization
in: header
required: true
schema:
type: string
- name: id
in: path
required: true
schema:
type: string
responses:
'204':
description: API key deleted
"/api/v3/admin/api_keys/{id}/revoke":
patch:
summary: Revoke an API key
tags:
- API Keys
security:
- api_key: []
bearer_auth: []
description: |-
Marks the key revoked. Future requests using its token will fail; the row is preserved for audit.
**Required scope:** `write_api_keys` (for API-key authentication).
x-codeSamples:
- lang: javascript
label: Spree Admin SDK
source: |-
import { createAdminClient } from '@spree/admin-sdk'
const client = createAdminClient({
baseUrl: 'https://your-store.com',
secretKey: 'sk_xxx',
})
const key = await client.apiKeys.revoke('key_xxx')
parameters:
- name: x-spree-api-key
in: header
required: true
schema:
type: string
- name: Authoriza
# --- truncated at 32 KB (588 KB total) ---
# Full source: https://raw.githubusercontent.com/api-evangelist/spree/refs/heads/main/openapi/spree-admin-api.yaml