Spree Commerce Products API
Products, taxons/categories, product custom field values, and bulk product operations
Products, taxons/categories, product custom field values, and bulk product operations
openapi: 3.0.3
info:
title: Admin Account / Address Products 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: Products
description: Products, taxons/categories, product custom field values, and bulk product operations
paths:
/api/v3/admin/products/{product_id}/custom_fields:
get:
summary: List product custom fields
tags:
- Products
security:
- api_key: []
bearer_auth: []
description: 'Returns the product''s custom field values.
**Required scope:** `read_products` (for API-key authentication).'
x-codeSamples:
- lang: javascript
label: Spree Admin SDK
source: "import { createAdminClient } from '@spree/admin-sdk'\n\nconst client = createAdminClient({\n baseUrl: 'https://your-store.com',\n secretKey: 'sk_xxx',\n})\n\nconst { data: customFields } = await client.products.customFields.list('prod_UkLWZg9DAJ')"
parameters:
- name: x-spree-api-key
in: header
required: true
schema:
type: string
- name: Authorization
in: header
required: true
schema:
type: string
- name: product_id
in: path
required: true
schema:
type: string
- name: expand
in: query
required: false
description: Comma-separated associations to expand (e.g., custom_field_definition). Use dot notation for nested expand (max 4 levels).
schema:
type: string
- name: fields
in: query
required: false
description: Comma-separated list of fields to include (e.g., key,value,namespace). id is always included.
schema:
type: string
responses:
'200':
description: custom fields found
content:
application/json:
example:
data:
- id: cf_UkLWZg9DAJ
label: Title
type: Spree::Metafields::ShortText
field_type: short_text
key: custom.title
value: wool
created_at: '2026-06-12T17:25:00.143Z'
updated_at: '2026-06-12T17:25:00.143Z'
storefront_visible: true
custom_field_definition_id: cfdef_UkLWZg9DAJ
meta:
page: 1
limit: 25
count: 1
pages: 1
from: 1
to: 1
in: 1
previous: null
next: null
post:
summary: Create a product custom field
tags:
- Products
security:
- api_key: []
bearer_auth: []
description: 'Sets a custom field value on the product. Requires an existing CustomFieldDefinition; pass its prefixed `cfdef_…` id.
**Required scope:** `write_products` (for API-key authentication).'
x-codeSamples:
- lang: javascript
label: Spree Admin SDK
source: "import { createAdminClient } from '@spree/admin-sdk'\n\nconst client = createAdminClient({\n baseUrl: 'https://your-store.com',\n secretKey: 'sk_xxx',\n})\n\nconst customField = await client.products.customFields.create('prod_UkLWZg9DAJ', {\n custom_field_definition_id: 'cfdef_AbC123XyZ',\n value: 'wool',\n})"
parameters:
- name: x-spree-api-key
in: header
required: true
schema:
type: string
- name: Authorization
in: header
required: true
schema:
type: string
- name: product_id
in: path
required: true
schema:
type: string
responses:
'201':
description: custom field created
content:
application/json:
example:
id: cf_gbHJdmfrXB
label: Description
type: Spree::Metafields::LongText
field_type: long_text
key: custom.description
value: A longer description
created_at: '2026-06-12T17:25:00.791Z'
updated_at: '2026-06-12T17:25:00.791Z'
storefront_visible: true
custom_field_definition_id: cfdef_gbHJdmfrXB
'422':
description: duplicate definition for the same product
content:
application/json:
example:
error:
code: validation_error
message: Metafield definition has already been taken
details:
metafield_definition_id:
- has already been taken
requestBody:
content:
application/json:
schema:
type: object
required:
- custom_field_definition_id
- value
properties:
custom_field_definition_id:
type: string
description: Prefixed `cfdef_…` id
value:
description: Value matching the definition's `field_type`
/api/v3/admin/products/{product_id}/custom_fields/{id}:
get:
summary: Show a product custom field
tags:
- Products
security:
- api_key: []
bearer_auth: []
description: '**Required scope:** `read_products` (for API-key authentication).'
parameters:
- name: x-spree-api-key
in: header
required: true
schema:
type: string
- name: Authorization
in: header
required: true
schema:
type: string
- name: product_id
in: path
required: true
schema:
type: string
- name: id
in: path
required: true
schema:
type: string
- name: expand
in: query
required: false
description: Comma-separated associations to expand (e.g., custom_field_definition). Use dot notation for nested expand (max 4 levels).
schema:
type: string
- name: fields
in: query
required: false
description: Comma-separated list of fields to include (e.g., key,value,namespace). id is always included.
schema:
type: string
responses:
'200':
description: custom field found
content:
application/json:
example:
id: cf_UkLWZg9DAJ
label: Title
type: Spree::Metafields::ShortText
field_type: short_text
key: custom.title
value: wool
created_at: '2026-06-12T17:25:01.167Z'
updated_at: '2026-06-12T17:25:01.167Z'
storefront_visible: true
custom_field_definition_id: cfdef_UkLWZg9DAJ
patch:
summary: Update a product custom field
tags:
- Products
security:
- api_key: []
bearer_auth: []
description: 'Updates the custom field''s `value`. The linked definition cannot be changed — delete and recreate to switch.
**Required scope:** `write_products` (for API-key authentication).'
parameters:
- name: x-spree-api-key
in: header
required: true
schema:
type: string
- name: Authorization
in: header
required: true
schema:
type: string
- name: product_id
in: path
required: true
schema:
type: string
- name: id
in: path
required: true
schema:
type: string
responses:
'200':
description: custom field updated
content:
application/json:
example:
id: cf_UkLWZg9DAJ
label: Title
type: Spree::Metafields::ShortText
field_type: short_text
key: custom.title
value: cotton
created_at: '2026-06-12T17:25:01.494Z'
updated_at: '2026-06-12T17:25:01.792Z'
storefront_visible: true
custom_field_definition_id: cfdef_UkLWZg9DAJ
requestBody:
content:
application/json:
schema:
type: object
required:
- value
properties:
value:
description: New value
delete:
summary: Delete a product custom field
tags:
- Products
security:
- api_key: []
bearer_auth: []
description: '**Required scope:** `write_products` (for API-key authentication).'
parameters:
- name: x-spree-api-key
in: header
required: true
schema:
type: string
- name: Authorization
in: header
required: true
schema:
type: string
- name: product_id
in: path
required: true
schema:
type: string
- name: id
in: path
required: true
schema:
type: string
responses:
'204':
description: custom field deleted
/api/v3/admin/products:
get:
summary: List products
tags:
- Products
security:
- api_key: []
bearer_auth: []
description: 'Returns a paginated list of products for the current store.
**Required scope:** `read_products` (for API-key authentication).'
x-codeSamples:
- lang: javascript
label: Spree Admin SDK
source: "import { createAdminClient } from '@spree/admin-sdk'\n\nconst client = createAdminClient({\n baseUrl: 'https://your-store.com',\n secretKey: 'sk_xxx',\n})\n\nconst { data: products } = await client.products.list({\n name_cont: 'shirt',\n status_eq: 'active',\n sort: '-created_at',\n limit: 25,\n})"
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: sort
in: query
required: false
description: Sort field (e.g., name, -name, price, -price, best_selling)
schema:
type: string
- name: expand
in: query
required: false
description: Comma-separated associations to expand (e.g., variants, media, option_types, categories). Use dot notation for nested expand (max 4 levels).
schema:
type: string
- name: fields
in: query
required: false
description: Comma-separated list of fields to include (e.g., name,slug,price,status). id is always included.
schema:
type: string
- name: q[name_cont]
in: query
required: false
description: Filter by name (contains)
schema:
type: string
- name: q[status_eq]
in: query
required: false
description: Filter by status
schema:
type: string
responses:
'200':
description: products found
content:
application/json:
example:
data:
- id: prod_UkLWZg9DAJ
name: Product 572113
slug: product-572113
meta_title: null
meta_description: null
meta_keywords: null
variant_count: 0
available_on: null
purchasable: true
in_stock: false
backorderable: true
available: true
description: Optio eum culpa id nostrum aspernatur tenetur asperiores. Qui quos quod laudantium tempora sequi. Adipisci sequi neque commodi tenetur. Veritatis doloremque fugiat quae accusantium inventore perferendis provident laudantium. Similique officiis quasi saepe reiciendis quam molestias.
description_html: Optio eum culpa id nostrum aspernatur tenetur asperiores. Qui quos quod laudantium tempora sequi. Adipisci sequi neque commodi tenetur. Veritatis doloremque fugiat quae accusantium inventore perferendis provident laudantium. Similique officiis quasi saepe reiciendis quam molestias.
default_variant_id: variant_UkLWZg9DAJ
thumbnail_url: null
tags: []
price:
id: price_UkLWZg9DAJ
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
variant_id: variant_UkLWZg9DAJ
created_at: '2026-06-12T17:25:02.180Z'
updated_at: '2026-06-12T17:25:02.180Z'
original_price: null
status: active
metadata: {}
deleted_at: null
created_at: '2026-06-12T17:25:02.168Z'
updated_at: '2026-06-12T17:25:02.188Z'
tax_category_id: taxcat_UkLWZg9DAJ
meta:
page: 1
limit: 25
count: 1
pages: 1
from: 1
to: 1
in: 1
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
content:
application/json:
example:
error:
code: authentication_required
message: Authentication required
schema:
$ref: '#/components/schemas/ErrorResponse'
post:
summary: Create a product
tags:
- Products
security:
- api_key: []
bearer_auth: []
description: 'Creates a new product. Supports nested variants with prices and option types.
Option types and values are auto-created if they don''t exist.
Prices are upserted by currency. Stock items are upserted by stock location.
**Required scope:** `write_products` (for API-key authentication).'
x-codeSamples:
- lang: javascript
label: Spree Admin SDK
source: "import { createAdminClient } from '@spree/admin-sdk'\n\nconst client = createAdminClient({\n baseUrl: 'https://your-store.com',\n secretKey: 'sk_xxx',\n})\n\nconst product = await client.products.create({\n name: 'Premium T-Shirt',\n description: 'Soft, organic cotton.',\n status: 'active',\n variants: [\n {\n sku: 'TSHIRT-S-NAVY',\n options: [\n { name: 'size', value: 'Small' },\n { name: 'color', value: 'navy' },\n ],\n prices: [\n { currency: 'USD', amount: 29.99 },\n { currency: 'EUR', amount: 27.99 },\n ],\n stock_items: [\n { stock_location_id: 'sloc_UkLWZg9DAJ', count_on_hand: 50 },\n ],\n },\n {\n sku: 'TSHIRT-M-NAVY',\n options: [\n { name: 'size', value: 'Medium' },\n { name: 'color', value: 'navy' },\n ],\n prices: [{ currency: 'USD', amount: 29.99 }],\n stock_items: [\n { stock_location_id: 'sloc_UkLWZg9DAJ', count_on_hand: 30 },\n ],\n },\n ],\n})"
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: product created
content:
application/json:
example:
id: prod_gbHJdmfrXB
name: New Product
slug: new-product
meta_title: null
meta_description: null
meta_keywords: null
variant_count: 0
available_on: null
purchasable: false
in_stock: false
backorderable: false
available: false
description: null
description_html: null
default_variant_id: variant_gbHJdmfrXB
thumbnail_url: null
tags: []
price: null
original_price: null
status: draft
metadata: {}
deleted_at: null
created_at: '2026-06-12T17:25:02.877Z'
updated_at: '2026-06-12T17:25:02.878Z'
tax_category_id: null
schema:
$ref: '#/components/schemas/Product'
'422':
description: validation error
content:
application/json:
example:
error:
code: validation_error
message: Name can't be blank
details:
name:
- can't be blank
schema:
$ref: '#/components/schemas/ErrorResponse'
requestBody:
content:
application/json:
schema:
type: object
properties:
name:
type: string
example: Premium T-Shirt
description:
type: string
slug:
type: string
status:
type: string
enum:
- draft
- active
- archived
tax_category_id:
type: string
description: Tax category ID
category_ids:
type: array
items:
type: string
description: Array of category IDs
tags:
type: array
items:
type: string
example:
- eco
- sale
variants:
type: array
description: Array of variant payloads. Variants can declare multiple option pairs via `options:` and per-currency prices via `prices:`. Stock counts go in `stock_items:` (per stock location).
items:
type: object
properties:
sku:
type: string
options:
type: array
description: One pair per option type the variant participates in (e.g. size + color). Option types and values are auto-created if missing.
items:
type: object
required:
- name
- value
properties:
name:
type: string
example: size
value:
type: string
example: Small
prices:
type: array
description: Per-currency prices. Upserted by currency.
items:
type: object
required:
- currency
- amount
properties:
currency:
type: string
example: USD
amount:
type: number
example: 29.99
compare_at_amount:
type: number
example: 39.99
stock_items:
type: array
description: Per-stock-location inventory. Upserted by stock_location_id.
items:
type: object
required:
- stock_location_id
- count_on_hand
properties:
stock_location_id:
type: string
description: Stock location ID (e.g. sloc_xxx)
count_on_hand:
type: integer
example: 50
backorderable:
type: boolean
required:
- name
/api/v3/admin/products/{id}:
get:
summary: Get a product
tags:
- Products
security:
- api_key: []
bearer_auth: []
description: 'Returns a single product by ID.
**Required scope:** `read_products` (for API-key authentication).'
x-codeSamples:
- lang: javascript
label: Spree Admin SDK
source: "import { createAdminClient } from '@spree/admin-sdk'\n\nconst client = createAdminClient({\n baseUrl: 'https://your-store.com',\n secretKey: 'sk_xxx',\n})\n\nconst product = await client.products.get('prod_86Rf07xd4z', {\n expand: ['variants', 'option_types'],\n})"
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: id
in: path
required: true
description: Product ID (e.g., prod_xxx)
schema:
type: string
- name: expand
in: query
required: false
description: Comma-separated associations to expand (e.g., variants, media, option_types, categories). Use dot notation for nested expand (max 4 levels).
schema:
type: string
- name: fields
in: query
required: false
description: Comma-separated list of fields to include (e.g., name,slug,price,status). id is always included.
schema:
type: string
responses:
'200':
description: product found
content:
application/json:
example:
id: prod_UkLWZg9DAJ
name: Product 61236
slug: product-61236
meta_title: null
meta_description: null
meta_keywords: null
variant_count: 0
available_on: null
purchasable: true
in_stock: false
backorderable: true
available: true
description: Minima nisi impedit aut sed aspernatur repellendus ullam quisquam. Quod ab vero occaecati quasi. Quasi in tempore iure aut praesentium reprehenderit repellat. Voluptatem fugit eius natus quisquam rerum animi placeat cumque. Pariatur quaerat porro possimus animi at facere eaque. Molestias accusamus neque officiis ut aspernatur totam saepe. Debitis at voluptatibus libero ducimus totam non asperiores reprehenderit. Veritatis eos iusto quis sapiente magnam. Dignissimos inventore quo et ipsam reprehenderit praesentium vitae minima. Inventore eaque praesentium earum ipsam consequatur. Corporis voluptatem iure ipsam iste perferendis quaerat at sit. Nesciunt laudantium itaque necessitatibus possimus provident enim quos. Aperiam a quis aliquam quas necessitatibus eos ut fuga. Quaerat labore nobis animi molestiae. Consequuntur rem molestias distinctio ipsa.
description_html: 'Minima nisi impedit aut sed aspernatur repellendus ullam quisquam. Quod ab vero occaecati quasi. Quasi in tempore iure aut praesentium reprehenderit repellat. Voluptatem fugit eius natus quisquam rerum animi placeat cumque.
Pariatur quaerat porro possimus animi at facere eaque. Molestias accusamus neque officiis ut aspernatur totam saepe. Debitis at voluptatibus libero ducimus totam non asperiores reprehenderit. Veritatis eos iusto quis sapiente magnam.
Dignissimos inventore quo et ipsam reprehenderit praesentium vitae minima. Inventore eaque praesentium earum ipsam consequatur. Corporis voluptatem iure ipsam iste perferendis quaerat at sit. Nesciunt laudantium itaque necessitatibus possimus provident enim quos.
Aperiam a quis aliquam quas necessitatibus eos ut fuga. Quaerat labore nobis animi molestiae. Consequuntur rem molestias distinctio ipsa.'
default_variant_id: variant_UkLWZg9DAJ
thumbnail_url: null
tags: []
price:
id: price_UkLWZg9DAJ
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
variant_id: variant_UkLWZg9DAJ
created_at: '2026-06-12T17:25:03.263Z'
updated_at: '2026-06-12T17:25:03.263Z'
original_price: null
status: active
metadata: {}
deleted_at: null
created_at: '2026-06-12T17:25:03.252Z'
updated_at: '2026-06-12T17:25:03.269Z'
tax_category_id: taxcat_UkLWZg9DAJ
schema:
$ref: '#/components/schemas/Product'
'404':
description: product not found
content:
application/json:
example:
error:
code: record_not_found
message: Product not found
schema:
$ref: '#/components/schemas/ErrorResponse'
patch:
summary: Update a product
tags:
- Products
security:
- api_key: []
bearer_auth: []
description: 'Updates a product. Only provided fields are updated.
**Required scope:** `write_products` (for API-key authentication).'
x-codeSamples:
- lang: javascript
label: Spree Admin SDK
source: "import { createAdminClient } from '@spree/admin-sdk'\n\nconst client = createAdminClient({\n baseUrl: 'https://your-store.com',\n secretKey: 'sk_xxx',\n})\n\nconst product = await client.products.update('prod_86Rf07xd4z', {\n name: 'Updated Name',\n status: 'active',\n tags: ['eco', 'sale'],\n})"
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: id
in: path
required: true
description: Product ID
schema:
type: string
responses:
'200':
description: product updated
content:
application/json:
example:
id: prod_UkLWZg9DAJ
name: Updated Name
slug: product-637715
meta_title: null
meta_description: null
meta_keywords: null
variant_count: 0
available_on: null
purchasable: true
in_stock: false
backorderable: true
available: true
description: Minima esse id porro impedit aliquid quam sint. Vero exercitationem voluptate consequatur fuga eos dolorum corrupti recusandae. Non similique facere beatae repudiandae exercitationem corrupti facilis suscipit. Impedit distinctio expedita tempora itaque. Omnis quisquam eaque iure alias officia. Consequatur molestiae error nostrum consectetur sint perferendis. Nesciunt fugit dolorem dolorum sed totam nulla occaecati. Qui quas aliquam occaecati harum consequatur ratione temporibus perspiciatis. Eos labore officia commodi quisquam. Odio expedita necessitatibus minus non rem pariatur dignissimos voluptatum. Illum molestiae totam perferendis repudiandae consectetur maxime. Tenetur architecto qui voluptatem culpa sint aspernatur eum voluptates. Suscipit reiciendis eveniet error quae temporibus enim. Atque quia repellat sequi dignissimos nostrum soluta perspiciatis aliquam. Iste quis similique eaque in. Sint voluptatum saepe pariatur aspernatur est nisi odio. Sequi atque porro nihil possimus dolorem eum. Laboriosam consectetur vitae iusto autem tempora doloremque non. Sint rerum esse cumque reprehenderit consequuntur ad non ratione. Earum quo quae reiciendis facere.
description_html: 'Minima esse id porro impedit aliquid quam sint. Vero exercitationem voluptate consequatur fuga eos dolorum corrupti recusandae. Non similique facere beatae repudiandae exercitationem corrupti facilis suscipit.
Impedit distinctio expedita tempora itaque. Omnis quisquam eaque iure alias officia. Consequatur molestiae error nostrum consectetur sint perferendis. Nesciunt fugit dolorem dolorum sed totam nulla occaecati. Qui quas aliquam occaecati harum consequatur ratione temporibus perspiciatis.
Eos labore officia commodi quisquam. Odio expedita necessitatibus minus non rem pariatur dignissimos voluptatum. Illum molestiae totam perferendis repudiandae consectetur maxime. Tenetur architecto qui voluptatem culpa sint aspernatur eum voluptates.
Suscipit reiciendis eveniet error quae temporibus enim. Atque quia repellat sequi dignissimos nostrum soluta perspiciatis aliquam. Iste quis similique eaque in.
Sint voluptatum saepe pariatur aspernatur est nisi odio. Sequi atque porro nihil possimus dolorem eum. Laboriosam consectetur vitae iusto autem tempora doloremque non. Sint rerum esse cumque reprehenderit consequuntur ad non ratione. Earum quo quae reiciendis facere.'
default_variant_id: variant_UkLWZg9DAJ
thumbnail_url: null
tags: []
price:
id: price_UkLWZg9DAJ
amount: '19.99'
amount_in_cents: 1999
compare_at_amount: null
compare_at_amount_
# --- truncated at 32 KB (208 KB total) ---
# Full source: https://raw.githubusercontent.com/api-evangelist/spree/refs/heads/main/openapi/spree-products-api-openapi.yml