Spree Commerce Product Catalog API
Products and categories
Products and categories
openapi: 3.0.3
info:
title: Admin Account / Address Product Catalog API
contact:
name: Spree Commerce
url: https://spreecommerce.org
email: hello@spreecommerce.org
description: "Spree Admin API v3 - Administrative API for managing products, orders, and store settings.\n\n## Authentication\n\nThe Admin API requires a secret API key passed in the `x-spree-api-key` header.\nSecret API keys can be generated in the Spree admin dashboard.\n\n## Response Format\n\nAll responses are JSON. List endpoints return paginated responses with `data` and `meta` keys.\nSingle resource endpoints return a flat JSON object.\n\n## Resource IDs\n\nEvery resource is identified by an opaque string ID (e.g. `prod_86Rf07xd4z`,\n`variant_k5nR8xLq`, `or_UkLWZg9DAJ`). Use these IDs everywhere — URL paths,\nrequest bodies, and Ransack filters all accept them directly.\n\n## Error Handling\n\nErrors return a consistent format:\n```json\n{\n \"error\": {\n \"code\": \"validation_error\",\n \"message\": \"Validation failed\",\n \"details\": { \"name\": [\"can't be blank\"] }\n }\n}\n```\n"
version: v3
servers:
- url: http://{defaultHost}
variables:
defaultHost:
default: localhost:3000
tags:
- name: Product Catalog
description: Products and categories
paths:
/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'\n\nconst client = createClient({\n baseUrl: 'https://your-store.com',\n publishableKey: '<api-key>',\n})\n\nconst categories = await client.categories.list({\n page: 1,\n limit: 25,\n})"
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: null
meta_description: null
meta_keywords: null
children_count: 1
parent_id: null
description: ''
description_html: ''
image_url: null
square_image_url: null
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: null
meta_description: null
meta_keywords: null
children_count: 1
parent_id: ctg_UkLWZg9DAJ
description: ''
description_html: ''
image_url: null
square_image_url: null
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: null
meta_description: null
meta_keywords: null
children_count: 0
parent_id: ctg_gbHJdmfrXB
description: ''
description_html: ''
image_url: null
square_image_url: null
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: null
next: null
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'\n\nconst client = createClient({\n baseUrl: 'https://your-store.com',\n publishableKey: '<api-key>',\n})\n\nconst category = await client.categories.get('categories/clothing/shirts', {\n expand: ['children'],\n})"
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: null
meta_description: null
meta_keywords: null
children_count: 1
parent_id: ctg_UkLWZg9DAJ
description: ''
description_html: ''
image_url: null
square_image_url: null
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'\n\nconst client = createClient({\n baseUrl: 'https://your-store.com',\n publishableKey: '<api-key>',\n})\n\nconst products = await client.products.list({\n page: 1,\n limit: 25,\n sort: 'price',\n name_cont: 'shirt',\n price_gte: 20,\n price_lte: 100,\n with_option_value_ids: ['optval_abc', 'optval_def'],\n expand: ['variants', 'media'],\n})"
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: null
meta_description: null
meta_keywords: null
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: null
tags: []
price:
id: price_gbHJdmfrXB
amount: '19.99'
amount_in_cents: 1999
compare_at_amount: null
compare_at_amount_in_cents: null
currency: USD
display_amount: $19.99
display_compare_at_amount: null
price_list_id: null
original_price: null
- id: prod_gbHJdmfrXB
name: Product 1435220
slug: product-1435220
meta_title: null
meta_description: null
meta_keywords: null
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: null
tags: []
price:
id: price_EfhxLZ9ck8
amount: '19.99'
amount_in_cents: 1999
compare_at_amount: null
compare_at_amount_in_cents: null
currency: USD
display_amount: $19.99
display_compare_at_amount: null
price_list_id: null
original_price: null
meta:
page: 1
limit: 25
count: 2
pages: 1
from: 1
to: 2
in: 2
previous: null
next: null
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'\n\nconst client = createClient({\n baseUrl: 'https://your-store.com',\n publishableKey: '<api-key>',\n})\n\nconst product = await client.products.get('spree-tote', {\n expand: ['variants', 'media'],\n})"
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:
type: string
- name: expand
in: query
required: false
description: Comma-separated associations to expand
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: product without prior_price expanded does not include field
content:
application/json:
example:
id: prod_UkLWZg9DAJ
name: Product 1702106
slug: product-1702106
meta_title: null
meta_description: null
meta_keywords: null
variant_count: 1
available_on: '2025-05-26T16:14:24.749Z'
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: null
tags: []
price:
id: price_gbHJdmfrXB
amount: '19.99'
amount_in_cents: 1999
compare_at_amount: null
compare_at_amount_in_cents: null
currency: USD
display_amount: $19.99
display_compare_at_amount: null
price_list_id: null
original_price: null
prior_price:
id: _AXs1igzRC6
amount: '9.99'
amount_in_cents: 999
currency: USD
display_amount: $9.99
recorded_at: '2026-05-11T16:14:24Z'
schema:
$ref: '#/components/schemas/Product'
'404':
description: draft product not visible
content:
application/json:
example:
error:
code: record_not_found
message: Product not found
schema:
$ref: '#/components/schemas/ErrorResponse'
/api/v3/store/products/filters:
get:
summary: Get product filters
tags:
- Product Catalog
security:
- api_key: []
description: 'Returns available filters for products with their options and counts.
Use this endpoint to build filter UIs for product listing pages.
The filters are context-aware - when a category_id is provided, only filters
relevant to products in that category are returned.
'
x-codeSamples:
- lang: javascript
label: Spree SDK
source: "import { createClient } from '@spree/sdk'\n\nconst client = createClient({\n baseUrl: 'https://your-store.com',\n publishableKey: '<api-key>',\n})\n\nconst filters = await client.products.filters({\n category_id: 'ctg_abc123',\n})"
parameters:
- name: x-spree-api-key
in: header
required: true
description: Publishable API key
schema:
type: string
- name: category_id
in: query
required: false
description: Scope filters to products in this category (prefix ID)
schema:
type: string
- name: q[name_cont]
in: query
required: false
description: Filter by name containing string
schema:
type: string
responses:
'200':
description: filters scoped to category
content:
application/json:
example:
filters:
- id: price
type: price_range
min: 19.99
max: 19.99
currency: USD
- id: availability
type: availability
options:
- id: in_stock
count: 2
- id: out_of_stock
count: 0
- id: opt_UkLWZg9DAJ
type: option
name: size
label: Size
kind: dropdown
options:
- id: optval_UkLWZg9DAJ
name: small
label: S
position: 1
color_code: null
image_url: null
count: 1
- id: categories
type: category
options:
- id: ctg_EfhxLZ9ck8
name: Shirts
permalink: taxonomy-27/taxon-34/shirts
count: 2
sort_options:
- id: manual
- id: best_selling
- id: price
- id: -price
- id: -available_on
- id: available_on
- id: name
- id: -name
default_sort: manual
total_count: 2
schema:
type: object
properties:
filters:
type: array
description: Available filters (price_range, availability, option, category)
items:
type: object
sort_options:
type: array
description: Available sort options
items:
type: object
properties:
id:
type: string
required:
- id
default_sort:
type: string
description: Default sort option ID
total_count:
type: integer
description: Total products matching current filters
required:
- filters
- sort_options
- default_sort
- total_count
'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'
components:
schemas:
Price:
type: object
properties:
id:
type: string
amount:
type: string
nullable: true
amount_in_cents:
type: number
nullable: true
compare_at_amount:
type: string
nullable: true
compare_at_amount_in_cents:
type: number
nullable: true
currency:
type: string
nullable: true
display_amount:
type: string
nullable: true
display_compare_at_amount:
type: string
nullable: true
price_list_id:
type: string
nullable: true
required:
- id
- amount
- amount_in_cents
- compare_at_amount
- compare_at_amount_in_cents
- currency
- display_amount
- display_compare_at_amount
- price_list_id
x-typelizer: true
OptionValue:
type: object
properties:
id:
type: string
option_type_id:
type: string
name:
type: string
label:
type: string
position:
type: number
color_code:
type: string
nullable: true
option_type_name:
type: string
option_type_label:
type: string
image_url:
type: string
nullable: true
required:
- id
- option_type_id
- name
- label
- position
- color_code
- option_type_name
- option_type_label
- image_url
x-typelizer: true
PriceHistory:
type: object
properties:
id:
type: string
amount:
type: string
amount_in_cents:
type: number
currency:
type: string
display_amount:
type: string
recorded_at:
type: string
required:
- id
- amount
- amount_in_cents
- currency
- display_amount
- recorded_at
x-typelizer: true
OptionType:
type: object
properties:
id:
type: string
name:
type: string
label:
type: string
position:
type: number
kind:
type: string
required:
- id
- name
- label
- position
- kind
x-typelizer: true
Category:
type: object
properties:
id:
type: string
name:
type: string
permalink:
type: string
position:
type: number
depth:
type: number
meta_title:
type: string
nullable: true
meta_description:
type: string
nullable: true
meta_keywords:
type: string
nullable: true
children_count:
type: number
parent_id:
type: string
nullable: true
description:
type: string
description_html:
type: string
image_url:
type: string
nullable: true
square_image_url:
type: string
nullable: true
is_root:
type: boolean
is_child:
type: boolean
is_leaf:
type: boolean
parent:
$ref: '#/components/schemas/Category'
children:
type: array
items:
$ref: '#/components/schemas/Category'
ancestors:
type: array
items:
$ref: '#/components/schemas/Category'
custom_fields:
type: array
items:
$ref: '#/components/schemas/CustomField'
required:
- id
- name
- permalink
- position
- depth
- meta_title
- meta_description
- meta_keywords
- children_count
- parent_id
- description
- description_html
- image_url
- square_image_url
- is_root
- is_child
- is_leaf
x-typelizer: true
PaginationMeta:
type: object
properties:
page:
type: integer
example: 1
limit:
type: integer
example: 25
count:
type: integer
example: 100
description: Total number of records
pages:
type: integer
example: 4
description: Total number of pages
from:
type: integer
example: 1
description: Index of first record on this page
to:
type: integer
example: 25
description: Index of last record on this page
in:
type: integer
example: 25
description: Number of records on this page
previous:
type: integer
nullable: true
example: null
description: Previous page number
next:
type: integer
nullable: true
example: 2
description: Next page number
required:
- page
- limit
- count
- pages
- from
- to
- in
Product:
type: object
properties:
id:
type: string
name:
type: string
slug:
type: string
meta_title:
type: string
nullable: true
meta_description:
type: string
nullable: true
meta_keywords:
type: string
nullable: true
variant_count:
type: number
available_on:
type: string
nullable: true
purchasable:
type: boolean
in_stock:
type: boolean
backorderable:
type: boolean
available:
type: boolean
description:
type: string
nullable: true
description_html:
type: string
nullable: true
default_variant_id:
type: string
thumbnail_url:
type: string
nullable: true
tags:
type: array
items:
type: string
price:
$ref: '#/components/schemas/Price'
original_price:
allOf:
- $ref: '#/components/schemas/Price'
nullable: true
primary_media:
$ref: '#/components/schemas/Media'
media:
type: array
items:
$ref: '#/components/schemas/Media'
variants:
type: array
items:
$ref: '#/components/schemas/Variant'
default_variant:
$ref: '#/components/schemas/Variant'
option_types:
type: array
items:
$ref: '#/components/schemas/OptionType'
categories:
type: array
items:
$ref: '#/components/schemas/Category'
custom_fields:
type: array
items:
$ref: '#/components/schemas/CustomField'
prior_price:
allOf:
- $ref: '#/components/schemas/PriceHistory'
nullable: true
required:
- id
- name
- slug
- meta_title
- meta_description
- meta_keywords
- variant_count
- available_on
- purchasable
- in_stock
- backorderable
- available
- description
- description_html
- default_variant_id
- thumbnail_url
- tags
- price
- original_price
x-typelizer: true
ErrorResponse:
type: object
properties:
error:
type: object
properties:
code:
type: string
example: record_not_found
# --- truncated at 32 KB (37 KB total) ---
# Full source: https://raw.githubusercontent.com/api-evangelist/spree/refs/heads/main/openapi/spree-product-catalog-api-openapi.yml