Spree Store API
Customer-facing REST API for browsing products, managing carts, placing orders, and handling checkout. Documented with OpenAPI 3.0.
Customer-facing REST API for browsing products, managing carts, placing orders, and handling checkout. Documented with OpenAPI 3.0.
---
openapi: 3.0.3
info:
title: Store API
contact:
name: Spree Commerce
url: https://spreecommerce.org
email: [email protected]
description: |
Spree Store API v3 - Customer-facing storefront API for building headless commerce experiences.
## Authentication
The Store API uses two authentication methods:
### API Key (Required)
All requests must include a publishable API key in the `x-spree-api-key` header.
### JWT Bearer Token (For authenticated customers)
After login, include the JWT token in the `Authorization: Bearer <token>` header.
### Order Token (For guest checkout)
When creating an order, a `token` is returned. Include this in the `x-spree-token` header
for guest access to that specific order.
## Response Format
All responses are JSON. List endpoints return paginated responses with `data` and `meta` keys.
## Error Handling
Errors return a consistent format:
```json
{
"error": {
"code": "record_not_found",
"message": "Product not found"
}
}
```
version: v3
paths:
"/api/v3/store/auth/login":
post:
summary: Login
tags:
- Authentication
security:
- api_key: []
description: |
Authenticates a customer and returns a JWT access token + refresh token.
Dispatches by the `provider` field to a strategy registered in
`Spree.store_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 (Auth0, Okta, Firebase, 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 + refresh token regardless of which strategy authenticated
the request.
x-codeSamples:
- lang: javascript
label: Spree SDK
source: |-
import { createClient } from '@spree/sdk'
const client = createClient({
baseUrl: 'https://your-store.com',
publishableKey: '<api-key>',
})
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.eyJ1c2VyX2lkIjoxLCJ1c2VyX3R5cGUiOiJjdXN0b21lciIsImp0aSI6IjY0NjcwM2Q0LWY5ZjAtNDlmMi05ZmZkLTA3YTZhY2I5YWZkZiIsImlzcyI6InNwcmVlIiwiYXVkIjoic3RvcmVfYXBpIiwiZXhwIjoxNzc5ODE1NjE1fQ.jlz2KHxYkB1Dd9ucl26zy6E5M7dFB5q9g-Qw0YjsX50
refresh_token: MQ9QZ1ToR8QocZoDd4ggC8yN
user:
id: cus_UkLWZg9DAJ
email: [email protected]
first_name: Colette
last_name: Hegmann
phone:
accepts_email_marketing: false
full_name: Colette Hegmann
available_store_credit_total: '0'
display_available_store_credit_total: "$0.00"
addresses: []
default_billing_address:
default_shipping_address:
schema:
"$ref": "#/components/schemas/AuthResponse"
'401':
description: missing API key
content:
application/json:
example:
error:
code: invalid_token
message: Valid API key required
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: auth0
description: Registered provider key (anything other than `email`).
not:
enum:
- email
required:
- provider
additionalProperties: true
"/api/v3/store/auth/refresh":
post:
summary: Refresh token
tags:
- Authentication
security:
- api_key: []
description: Exchanges a refresh token for a new access JWT and rotated refresh
token. No Authorization header needed.
x-codeSamples:
- lang: javascript
label: Spree SDK
source: |-
import { createClient } from '@spree/sdk'
const client = createClient({
baseUrl: 'https://your-store.com',
publishableKey: '<api-key>',
})
const auth = await client.auth.refresh({
refresh_token: 'rt_xxx',
})
parameters:
- name: x-spree-api-key
in: header
required: true
schema:
type: string
responses:
'200':
description: token refreshed
content:
application/json:
example:
token: eyJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoxLCJ1c2VyX3R5cGUiOiJjdXN0b21lciIsImp0aSI6IjNmZjU3NWU0LTVmNmItNDVjOC04MzIzLTIzMDMyMzVjZTkzMiIsImlzcyI6InNwcmVlIiwiYXVkIjoic3RvcmVfYXBpIiwiZXhwIjoxNzc5ODE1NjE2fQ.OuCk-UNe-asA8DvK2yKMkp94BQz9PN_Z7_SnReeIRYE
refresh_token: qLuZDRo8LqywXFThqPM5V2Ug
user:
id: cus_UkLWZg9DAJ
email: [email protected]
first_name: Debi
last_name: Tillman
phone:
accepts_email_marketing: false
full_name: Debi Tillman
available_store_credit_total: '0'
display_available_store_credit_total: "$0.00"
addresses: []
default_billing_address:
default_shipping_address:
schema:
"$ref": "#/components/schemas/AuthResponse"
'401':
description: missing or invalid refresh token
content:
application/json:
example:
error:
code: invalid_refresh_token
message: Invalid or expired refresh token
schema:
"$ref": "#/components/schemas/ErrorResponse"
requestBody:
content:
application/json:
schema:
type: object
properties:
refresh_token:
type: string
description: Refresh token from login response
required:
- refresh_token
"/api/v3/store/auth/logout":
post:
summary: Logout
tags:
- Authentication
security:
- api_key: []
description: Revokes the submitted refresh token. The refresh token itself is
the credential — no Authorization header is required, so a client with an
expired access JWT can still log out.
x-codeSamples:
- lang: javascript
label: Spree SDK
source: |-
import { createClient } from '@spree/sdk'
const client = createClient({
baseUrl: 'https://your-store.com',
publishableKey: '<api-key>',
})
await client.auth.logout({
refresh_token: 'rt_xxx',
})
parameters:
- name: x-spree-api-key
in: header
required: true
schema:
type: string
responses:
'204':
description: logout without refresh token (no-op)
requestBody:
content:
application/json:
schema:
type: object
properties:
refresh_token:
type: string
description: Refresh token to revoke
"/api/v3/store/password_resets":
post:
summary: Request a password reset
tags:
- Authentication
security:
- api_key: []
description: Sends a password reset email if an account exists for the given
email address. Always returns 202 Accepted to prevent email enumeration.
x-codeSamples:
- lang: javascript
label: Spree SDK
source: |-
import { createClient } from '@spree/sdk'
const client = createClient({
baseUrl: 'https://your-store.com',
publishableKey: '<api-key>',
})
await client.passwordResets.create({
email: '[email protected]',
redirect_url: 'https://myshop.com/reset-password',
})
parameters:
- name: x-spree-api-key
in: header
required: true
schema:
type: string
responses:
'202':
description: email not found (same response to prevent enumeration)
content:
application/json:
example:
message: If an account exists for that email, password reset instructions
have been sent.
schema:
type: object
properties:
message:
type: string
'401':
description: missing API key
content:
application/json:
example:
error:
code: invalid_token
message: Valid API key required
schema:
"$ref": "#/components/schemas/ErrorResponse"
requestBody:
content:
application/json:
schema:
type: object
properties:
email:
type: string
format: email
example: [email protected]
description: Email address of the account to reset
redirect_url:
type: string
format: uri
example: https://myshop.com/reset-password
description: URL to redirect the user to after clicking the reset
link. Validated against the store's allowed origins.
required:
- email
"/api/v3/store/password_resets/{token}":
patch:
summary: Reset password with token
tags:
- Authentication
security:
- api_key: []
description: Resets the password using a token received via email. Returns a
JWT token on success (auto-login).
x-codeSamples:
- lang: javascript
label: Spree SDK
source: |-
import { createClient } from '@spree/sdk'
const client = createClient({
baseUrl: 'https://your-store.com',
publishableKey: '<api-key>',
})
const auth = await client.passwordResets.update(
'reset-token-from-email',
{
password: 'newsecurepassword',
password_confirmation: 'newsecurepassword',
}
)
parameters:
- name: x-spree-api-key
in: header
required: true
schema:
type: string
- name: token
in: path
required: true
description: Password reset token from the email
schema:
type: string
responses:
'200':
description: password reset successful
content:
application/json:
example:
token: eyJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoxLCJ1c2VyX3R5cGUiOiJjdXN0b21lciIsImp0aSI6IjU1YjA5Yzc4LTUzMTYtNDRjYy05ZjJiLThmN2E2MzViMmRlNSIsImlzcyI6InNwcmVlIiwiYXVkIjoic3RvcmVfYXBpIiwiZXhwIjoxNzc5ODE1NjI4fQ.FX2Te4WfdAu3kN_fvvfHsH92_axvIZTI1d8Zmw8R1mE
refresh_token: Be2HUCjiJVRjgidFuYU7GRRh
user:
id: cus_UkLWZg9DAJ
email: [email protected]
first_name: Randa
last_name: O'Hara
phone:
accepts_email_marketing: false
full_name: Randa O'Hara
available_store_credit_total: '0'
display_available_store_credit_total: "$0.00"
addresses: []
default_billing_address:
default_shipping_address:
schema:
"$ref": "#/components/schemas/AuthResponse"
'422':
description: password confirmation mismatch
content:
application/json:
example:
error:
code: validation_error
message: Password confirmation doesn't match Password
details:
password_confirmation:
- doesn't match Password
schema:
"$ref": "#/components/schemas/ErrorResponse"
'401':
description: missing API key
content:
application/json:
example:
error:
code: invalid_token
message: Valid API key required
schema:
"$ref": "#/components/schemas/ErrorResponse"
requestBody:
content:
application/json:
schema:
type: object
properties:
password:
type: string
minLength: 6
example: newsecurepassword
password_confirmation:
type: string
example: newsecurepassword
required:
- password
- password_confirmation
"/api/v3/store/categories":
get:
summary: List categories
tags:
- Product Catalog
security:
- api_key: []
description: Returns a paginated list of categories for the current store
x-codeSamples:
- lang: javascript
label: Spree SDK
source: |-
import { createClient } from '@spree/sdk'
const client = createClient({
baseUrl: 'https://your-store.com',
publishableKey: '<api-key>',
})
const categories = await client.categories.list({
page: 1,
limit: 25,
})
parameters:
- name: x-spree-api-key
in: header
required: true
schema:
type: string
- name: page
in: query
required: false
schema:
type: integer
- name: limit
in: query
required: false
schema:
type: integer
- name: q[name_cont]
in: query
required: false
description: Filter by name
schema:
type: string
- name: fields
in: query
required: false
description: Comma-separated list of fields to include (e.g., name,slug,price).
id is always included.
schema:
type: string
responses:
'200':
description: categories found
content:
application/json:
example:
data:
- id: ctg_UkLWZg9DAJ
name: taxonomy_3
permalink: taxonomy-3
position: 0
depth: 0
meta_title:
meta_description:
meta_keywords:
children_count: 1
parent_id:
description: ''
description_html: ''
image_url:
square_image_url:
is_root: true
is_child: false
is_leaf: false
- id: ctg_gbHJdmfrXB
name: taxon_3
permalink: taxonomy-3/taxon-3
position: 0
depth: 1
meta_title:
meta_description:
meta_keywords:
children_count: 1
parent_id: ctg_UkLWZg9DAJ
description: ''
description_html: ''
image_url:
square_image_url:
is_root: false
is_child: true
is_leaf: false
- id: ctg_EfhxLZ9ck8
name: taxon_4
permalink: taxonomy-3/taxon-3/taxon-4
position: 0
depth: 2
meta_title:
meta_description:
meta_keywords:
children_count: 0
parent_id: ctg_gbHJdmfrXB
description: ''
description_html: ''
image_url:
square_image_url:
is_root: false
is_child: true
is_leaf: true
meta:
page: 1
limit: 25
count: 3
pages: 1
from: 1
to: 3
in: 3
previous:
next:
schema:
type: object
properties:
data:
type: array
items:
"$ref": "#/components/schemas/Category"
meta:
"$ref": "#/components/schemas/PaginationMeta"
'401':
description: unauthorized
content:
application/json:
example:
error:
code: invalid_token
message: Valid API key required
schema:
"$ref": "#/components/schemas/ErrorResponse"
"/api/v3/store/categories/{id}":
get:
summary: Get a category
tags:
- Product Catalog
security:
- api_key: []
description: Returns a single category by permalink or prefix ID
x-codeSamples:
- lang: javascript
label: Spree SDK
source: |-
import { createClient } from '@spree/sdk'
const client = createClient({
baseUrl: 'https://your-store.com',
publishableKey: '<api-key>',
})
const category = await client.categories.get('categories/clothing/shirts', {
expand: ['children'],
})
parameters:
- name: x-spree-api-key
in: header
required: true
schema:
type: string
- name: id
in: path
required: true
description: Category permalink (e.g., clothing/shirts) or prefix ID (e.g.,
ctg_abc123)
schema:
type: string
- name: expand
in: query
required: false
description: Expand associations (children, parent, ancestors, custom_fields)
schema:
type: string
- name: fields
in: query
required: false
description: Comma-separated list of fields to include (e.g., name,slug,price).
id is always included.
schema:
type: string
responses:
'200':
description: category found by prefix ID
content:
application/json:
example:
id: ctg_gbHJdmfrXB
name: taxon_12
permalink: taxonomy-9/taxon-12
position: 0
depth: 1
meta_title:
meta_description:
meta_keywords:
children_count: 1
parent_id: ctg_UkLWZg9DAJ
description: ''
description_html: ''
image_url:
square_image_url:
is_root: false
is_child: true
is_leaf: false
schema:
"$ref": "#/components/schemas/Category"
'404':
description: category from other store not accessible
content:
application/json:
example:
error:
code: record_not_found
message: Category not found
schema:
"$ref": "#/components/schemas/ErrorResponse"
'401':
description: unauthorized
content:
application/json:
example:
error:
code: invalid_token
message: Valid API key required
schema:
"$ref": "#/components/schemas/ErrorResponse"
"/api/v3/store/products":
get:
summary: List products
tags:
- Product Catalog
security:
- api_key: []
description: Returns a paginated list of active products for the current store
x-codeSamples:
- lang: javascript
label: Spree SDK
source: |-
import { createClient } from '@spree/sdk'
const client = createClient({
baseUrl: 'https://your-store.com',
publishableKey: '<api-key>',
})
const products = await client.products.list({
page: 1,
limit: 25,
sort: 'price',
name_cont: 'shirt',
price_gte: 20,
price_lte: 100,
with_option_value_ids: ['optval_abc', 'optval_def'],
expand: ['variants', 'media'],
})
parameters:
- name: x-spree-api-key
in: header
required: true
description: Publishable API key
schema:
type: string
- name: page
in: query
required: false
description: 'Page number (default: 1)'
schema:
type: integer
- name: limit
in: query
required: false
description: 'Number of items per page (default: 25, max: 100)'
schema:
type: integer
- name: sort
in: query
required: false
description: 'Sort order. Prefix with - for descending. Values: price, -price,
best_selling, name, -name, -available_on, available_on'
schema:
type: string
- name: q[name_cont]
in: query
required: false
description: Filter by name containing string
schema:
type: string
- name: q[in_category]
in: query
required: false
description: Filter by category prefixed ID (includes descendants)
schema:
type: string
- name: q[in_categories][]
in: query
required: false
description: Filter by multiple category prefixed IDs (OR logic, includes
descendants)
schema:
type: string
- name: q[price_gte]
in: query
required: false
description: Filter by minimum price
schema:
type: number
- name: q[price_lte]
in: query
required: false
description: Filter by maximum price
schema:
type: number
- name: q[with_option_value_ids][]
in: query
required: false
description: Filter by option value prefix IDs (e.g., optval_abc). Pass multiple
values for OR logic.
schema:
type: string
- name: q[in_stock]
in: query
required: false
description: Filter to only in-stock products
schema:
type: boolean
- name: expand
in: query
required: false
description: Comma-separated associations to expand (variants, media, categories,
option_types)
schema:
type: string
- name: fields
in: query
required: false
description: Comma-separated list of fields to include (e.g., name,slug,price).
id is always included.
schema:
type: string
responses:
'200':
description: products found
content:
application/json:
example:
data:
- id: prod_UkLWZg9DAJ
name: Product 1421251
slug: product-1421251
meta_title:
meta_description:
meta_keywords:
variant_count: 1
available_on: '2025-05-26T16:14:22.402Z'
purchasable: true
in_stock: false
backorderable: true
available: true
description: A comfortable cotton t-shirt.
description_html: "<p>A <strong>comfortable</strong> cotton t-shirt.</p>"
default_variant_id: variant_gbHJdmfrXB
thumbnail_url:
tags: []
price:
id: price_gbHJdmfrXB
amount: '19.99'
amount_in_cents: 1999
compare_at_amount:
compare_at_amount_in_cents:
currency: USD
display_amount: "$19.99"
display_compare_at_amount:
price_list_id:
original_price:
- id: prod_gbHJdmfrXB
name: Product 1435220
slug: product-1435220
meta_title:
meta_description:
meta_keywords:
variant_count: 0
available_on: '2025-05-26T16:14:22.472Z'
purchasable: true
in_stock: false
backorderable: true
available: true
description: Architecto dignissimos nemo inventore incidunt enim.
Odit accusamus repellat error saepe culpa unde eius. Cupiditate
officiis voluptatem autem perferendis qui vitae omnis sunt. Debitis
dolor tempore ad impedit itaque reprehenderit delectus. Doloremque
laudantium iure recusandae iusto debitis laborum consequuntur.
Impedit eum optio commodi tempora cum quidem. Facere voluptatum
nam possimus veritatis nostrum explicabo dolore ex. Adipisci iure
unde nobis itaque amet nesciunt voluptatibus impedit. Numquam
in accusantium magni itaque exercitationem. Quas vero maxime voluptatem
rem impedit inventore. Esse perferendis asperiores ea expedita
aut tenetur soluta. Enim accusantium dolor adipisci impedit. Error
maxime neque accusamus facere provident eum. Cum officia velit
asperiores quod cupiditate fugiat. Possimus tenetur aut consequuntur
iusto cumque quas. Ab velit ullam qui pariatur veritatis omnis.
Accusantium vero occaecati explicabo neque animi commodi. Necessitatibus
perspiciatis iste culpa totam quibusdam voluptate distinctio.
Quod aliquam ut et autem. Saepe ut asperiores et quisquam perspiciatis
molestiae. Vel recusandae sunt nemo et accusamus veniam ducimus.
Laborum modi quasi perferendis culpa laboriosam quaerat magnam.
Facere at tempora iusto ex magni aliquam modi debitis.
description_html: |-
Architecto dignissimos nemo inventore incidunt enim. Odit accusamus repellat error saepe culpa unde eius. Cupiditate officiis voluptatem autem perferendis qui vitae omnis sunt. Debitis dolor tempore ad impedit itaque reprehenderit delectus. Doloremque laudantium iure recusandae iusto debitis laborum consequuntur.
Impedit eum optio commodi tempora cum quidem. Facere voluptatum nam possimus veritatis nostrum explicabo dolore ex. Adipisci iure unde nobis itaque amet nesciunt voluptatibus impedit. Numquam in accusantium magni itaque exercitationem. Quas vero maxime voluptatem rem impedit inventore.
Esse perferendis asperiores ea expedita aut tenetur soluta. Enim accusantium dolor adipisci impedit. Error maxime neque accusamus facere provident eum. Cum officia velit asperiores quod cupiditate fugiat. Possimus tenetur aut consequuntur iusto cumque quas.
Ab velit ullam qui pariatur veritatis omnis. Accusantium vero occaecati explicabo neque animi commodi. Necessitatibus perspiciatis iste culpa totam quibusdam voluptate distinctio. Quod aliquam ut et autem. Saepe ut asperiores et quisquam perspiciatis molestiae.
Vel recusandae sunt nemo et accusamus veniam ducimus. Laborum modi quasi perferendis culpa laboriosam quaerat magnam. Facere at tempora iusto ex magni aliquam modi debitis.
default_variant_id: variant_EfhxLZ9ck8
thumbnail_url:
tags: []
price:
id: price_EfhxLZ9ck8
amount: '19.99'
amount_in_cents: 1999
compare_at_amount:
compare_at_amount_in_cents:
currency: USD
display_amount: "$19.99"
display_compare_at_amount:
price_list_id:
original_price:
meta:
page: 1
limit: 25
count: 2
pages: 1
from: 1
to: 2
in: 2
previous:
next:
schema:
type: object
properties:
data:
type: array
items:
"$ref": "#/components/schemas/Product"
meta:
"$ref": "#/components/schemas/PaginationMeta"
required:
- data
- meta
'401':
description: unauthorized - invalid or missing API key
content:
application/json:
example:
error:
code: invalid_token
message: Valid API key required
schema:
"$ref": "#/components/schemas/ErrorResponse"
"/api/v3/store/products/{id}":
get:
summary: Get a product
tags:
- Product Catalog
security:
- api_key: []
description: Returns a single product by slug or prefix ID
x-codeSamples:
- lang: javascript
label: Spree SDK
source: |-
import { createClient } from '@spree/sdk'
const client = createClient({
baseUrl: 'https://your-store.com',
publishableKey: '<api-key>',
})
const product = await client.products.get('spree-tote', {
expand: ['variants', 'media'],
})
parameters:
- name: x-spree-api-key
in: header
required: true
description: Publishable API key
schema:
type: string
- name: id
in: path
required: true
description: Product slug (e.g., spree-tote) or prefix ID (e.g., product_abc123)
schema:
# --- truncated at 32 KB (372 KB total) ---
# Full source: https://raw.githubusercontent.com/api-evangelist/spree/refs/heads/main/openapi/spree-store-api.yaml