Curate ordered collections of products with translations and hero media
/admin/api/series
List all series with product counts. By default hidden series are excluded; pass include_hidden=true to list everything.
/admin/api/series
Create a new series.
/admin/api/series/{id}
Get a single series, including per-locale translations.
/admin/api/series/{id}
Update a series. All fields are optional; omitted fields are unchanged.
/admin/api/series/{id}
Delete a series. Products belonging to the series are automatically unlinked (the foreign key is set to NULL).
/admin/api/series/{id}/products
List products in a series, ordered by the series's curated sort order.
/admin/api/series/{id}/products/order
Reorder products in a series. The product_ids array defines the new order — index 0 becomes series_sort_order 0, and so on. All listed products must already be members of the series.
/admin/api/series/{id}/products/{product_id}
Remove a product from the series. The product itself is not deleted — only its series association.
/admin/api/series
List all series with product counts. By default hidden series are excluded; pass include_hidden=true to list everything.
| Name | Type | Required | Description |
|---|---|---|---|
| include_hidden | boolean | optional | When true, include series where visible=false. |
{
"series": [
{
"id": 1,
"name": "Spring 2026",
"slug": "spring-2026",
"description": "Our Spring 2026 collection.",
"hero_media_id": 42,
"hero_media_url": "https://cdn.zeroshop.io/tenants/abc/media/spring-hero.jpg",
"visible": true,
"sort_order": 0,
"product_count": 18,
"created_at": "2026-02-01T08:00:00Z",
"updated_at": "2026-03-15T12:30:00Z"
},
{
"id": 2,
"name": "Founder Picks",
"slug": "founder-picks",
"description": "Hand-picked by the founder.",
"hero_media_id": null,
"hero_media_url": null,
"visible": true,
"sort_order": 1,
"product_count": 6,
"created_at": "2026-03-01T10:00:00Z",
"updated_at": "2026-03-01T10:00:00Z"
}
]
}
curl "https://yourshop.zeroshop.io/admin/api/series?include_hidden=true" \
-H "Authorization: Bearer zspat_..."
import requests
resp = requests.get(
"https://yourshop.zeroshop.io/admin/api/series",
params={"include_hidden": "true"},
headers={"Authorization": "Bearer zspat_..."}
)
data = resp.json()
const resp = await fetch(
"https://yourshop.zeroshop.io/admin/api/series?include_hidden=true",
{ headers: { "Authorization": "Bearer zspat_..." } }
);
const { series } = await resp.json();
require "net/http"
require "json"
uri = URI("https://yourshop.zeroshop.io/admin/api/series?include_hidden=true")
req = Net::HTTP::Get.new(uri)
req["Authorization"] = "Bearer zspat_..."
resp = Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) { |http| http.request(req) }
data = JSON.parse(resp.body)
/admin/api/series
Create a new series.
{
"name": "Spring 2026",
"slug": "spring-2026",
"description": "Our Spring 2026 collection.",
"hero_media_id": 42,
"visible": true,
"sort_order": 0,
"translations": {
"es": {
"name": "Primavera 2026",
"description": "Nuestra colección de primavera 2026."
}
}
}
{
"id": 1,
"name": "Spring 2026",
"slug": "spring-2026",
"description": "Our Spring 2026 collection.",
"hero_media_id": 42,
"hero_media_url": "https://cdn.zeroshop.io/tenants/abc/media/spring-hero.jpg",
"visible": true,
"sort_order": 0,
"product_count": 0,
"created_at": "2026-02-01T08:00:00Z",
"updated_at": "2026-02-01T08:00:00Z"
}
curl -X POST "https://yourshop.zeroshop.io/admin/api/series" \
-H "Authorization: Bearer zspat_..." \
-H "Content-Type: application/json" \
-d '{"name": "Spring 2026", "slug": "spring-2026", "visible": true}'
import requests
resp = requests.post(
"https://yourshop.zeroshop.io/admin/api/series",
headers={"Authorization": "Bearer zspat_..."},
json={"name": "Spring 2026", "slug": "spring-2026", "visible": True}
)
series = resp.json()
const resp = await fetch(
"https://yourshop.zeroshop.io/admin/api/series",
{
method: "POST",
headers: {
"Authorization": "Bearer zspat_...",
"Content-Type": "application/json"
},
body: JSON.stringify({ name: "Spring 2026", slug: "spring-2026", visible: true })
}
);
const series = await resp.json();
require "net/http"
require "json"
uri = URI("https://yourshop.zeroshop.io/admin/api/series")
req = Net::HTTP::Post.new(uri)
req["Authorization"] = "Bearer zspat_..."
req.content_type = "application/json"
req.body = { name: "Spring 2026", slug: "spring-2026", visible: true }.to_json
resp = Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) { |http| http.request(req) }
data = JSON.parse(resp.body)
/admin/api/series/{id}
Get a single series, including per-locale translations.
| Name | Type | Required | Description |
|---|---|---|---|
| id | integer | required | Series ID |
{
"id": 1,
"name": "Spring 2026",
"slug": "spring-2026",
"description": "Our Spring 2026 collection.",
"hero_media_id": 42,
"hero_media_url": "https://cdn.zeroshop.io/tenants/abc/media/spring-hero.jpg",
"visible": true,
"sort_order": 0,
"product_count": 18,
"translations": {
"es": {
"name": "Primavera 2026",
"description": "Nuestra colección de primavera 2026."
}
},
"created_at": "2026-02-01T08:00:00Z",
"updated_at": "2026-03-15T12:30:00Z"
}
curl "https://yourshop.zeroshop.io/admin/api/series/1" \
-H "Authorization: Bearer zspat_..."
import requests
resp = requests.get(
"https://yourshop.zeroshop.io/admin/api/series/1",
headers={"Authorization": "Bearer zspat_..."}
)
series = resp.json()
const resp = await fetch(
"https://yourshop.zeroshop.io/admin/api/series/1",
{ headers: { "Authorization": "Bearer zspat_..." } }
);
const series = await resp.json();
require "net/http"
require "json"
uri = URI("https://yourshop.zeroshop.io/admin/api/series/1")
req = Net::HTTP::Get.new(uri)
req["Authorization"] = "Bearer zspat_..."
resp = Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) { |http| http.request(req) }
data = JSON.parse(resp.body)
/admin/api/series/{id}
Update a series. All fields are optional; omitted fields are unchanged.
| Name | Type | Required | Description |
|---|---|---|---|
| id | integer | required | Series ID |
{
"name": "Spring/Summer 2026",
"description": "Expanded Spring/Summer edit.",
"hero_media_id": 58,
"visible": true,
"sort_order": 0
}
curl -X PUT "https://yourshop.zeroshop.io/admin/api/series/1" \
-H "Authorization: Bearer zspat_..." \
-H "Content-Type: application/json" \
-d '{"name": "Spring/Summer 2026", "visible": true}'
import requests
resp = requests.put(
"https://yourshop.zeroshop.io/admin/api/series/1",
headers={"Authorization": "Bearer zspat_..."},
json={"name": "Spring/Summer 2026", "visible": True}
)
# 204 No Content on success
const resp = await fetch(
"https://yourshop.zeroshop.io/admin/api/series/1",
{
method: "PUT",
headers: {
"Authorization": "Bearer zspat_...",
"Content-Type": "application/json"
},
body: JSON.stringify({ name: "Spring/Summer 2026", visible: true })
}
);
// 204 No Content on success
require "net/http"
require "json"
uri = URI("https://yourshop.zeroshop.io/admin/api/series/1")
req = Net::HTTP::Put.new(uri)
req["Authorization"] = "Bearer zspat_..."
req.content_type = "application/json"
req.body = { name: "Spring/Summer 2026", visible: true }.to_json
resp = Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) { |http| http.request(req) }
# 204 No Content on success
/admin/api/series/{id}
Delete a series. Products belonging to the series are automatically unlinked (the foreign key is set to NULL).
| Name | Type | Required | Description |
|---|---|---|---|
| id | integer | required | Series ID |
curl -X DELETE "https://yourshop.zeroshop.io/admin/api/series/1" \
-H "Authorization: Bearer zspat_..."
import requests
resp = requests.delete(
"https://yourshop.zeroshop.io/admin/api/series/1",
headers={"Authorization": "Bearer zspat_..."}
)
assert resp.status_code == 204
const resp = await fetch(
"https://yourshop.zeroshop.io/admin/api/series/1",
{
method: "DELETE",
headers: { "Authorization": "Bearer zspat_..." }
}
);
// 204 No Content
require "net/http"
uri = URI("https://yourshop.zeroshop.io/admin/api/series/1")
req = Net::HTTP::Delete.new(uri)
req["Authorization"] = "Bearer zspat_..."
resp = Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) { |http| http.request(req) }
# 204 No Content
/admin/api/series/{id}/products
List products in a series, ordered by the series's curated sort order.
| Name | Type | Required | Description |
|---|---|---|---|
| id | integer | required | Series ID |
{
"products": [
{
"id": 482,
"name": "Linen Shirt",
"slug": "linen-shirt",
"price_cents": 8999,
"primary_image_url": "https://cdn.zeroshop.io/tenants/abc/media/linen-shirt.jpg",
"series_sort_order": 0
},
{
"id": 491,
"name": "Chino Shorts",
"slug": "chino-shorts",
"price_cents": 6499,
"primary_image_url": "https://cdn.zeroshop.io/tenants/abc/media/chino-shorts.jpg",
"series_sort_order": 1
}
]
}
curl "https://yourshop.zeroshop.io/admin/api/series/1/products" \
-H "Authorization: Bearer zspat_..."
import requests
resp = requests.get(
"https://yourshop.zeroshop.io/admin/api/series/1/products",
headers={"Authorization": "Bearer zspat_..."}
)
products = resp.json()["products"]
const resp = await fetch(
"https://yourshop.zeroshop.io/admin/api/series/1/products",
{ headers: { "Authorization": "Bearer zspat_..." } }
);
const { products } = await resp.json();
require "net/http"
require "json"
uri = URI("https://yourshop.zeroshop.io/admin/api/series/1/products")
req = Net::HTTP::Get.new(uri)
req["Authorization"] = "Bearer zspat_..."
resp = Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) { |http| http.request(req) }
data = JSON.parse(resp.body)
/admin/api/series/{id}/products/order
Reorder products in a series. The product_ids array defines the new order — index 0 becomes series_sort_order 0, and so on. All listed products must already be members of the series.
| Name | Type | Required | Description |
|---|---|---|---|
| id | integer | required | Series ID |
{
"product_ids": [
491,
482,
507
]
}
curl -X PUT "https://yourshop.zeroshop.io/admin/api/series/1/products/order" \
-H "Authorization: Bearer zspat_..." \
-H "Content-Type: application/json" \
-d '{"product_ids": [491, 482, 507]}'
import requests
resp = requests.put(
"https://yourshop.zeroshop.io/admin/api/series/1/products/order",
headers={"Authorization": "Bearer zspat_..."},
json={"product_ids": [491, 482, 507]}
)
# 204 No Content on success
const resp = await fetch(
"https://yourshop.zeroshop.io/admin/api/series/1/products/order",
{
method: "PUT",
headers: {
"Authorization": "Bearer zspat_...",
"Content-Type": "application/json"
},
body: JSON.stringify({ product_ids: [491, 482, 507] })
}
);
// 204 No Content on success
require "net/http"
require "json"
uri = URI("https://yourshop.zeroshop.io/admin/api/series/1/products/order")
req = Net::HTTP::Put.new(uri)
req["Authorization"] = "Bearer zspat_..."
req.content_type = "application/json"
req.body = { product_ids: [491, 482, 507] }.to_json
resp = Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) { |http| http.request(req) }
# 204 No Content on success
/admin/api/series/{id}/products/{product_id}
Remove a product from the series. The product itself is not deleted — only its series association.
| Name | Type | Required | Description |
|---|---|---|---|
| id | integer | required | Series ID |
| product_id | integer | required | Product ID |
curl -X DELETE "https://yourshop.zeroshop.io/admin/api/series/1/products/482" \
-H "Authorization: Bearer zspat_..."
import requests
resp = requests.delete(
"https://yourshop.zeroshop.io/admin/api/series/1/products/482",
headers={"Authorization": "Bearer zspat_..."}
)
assert resp.status_code == 204
const resp = await fetch(
"https://yourshop.zeroshop.io/admin/api/series/1/products/482",
{
method: "DELETE",
headers: { "Authorization": "Bearer zspat_..." }
}
);
// 204 No Content
require "net/http"
uri = URI("https://yourshop.zeroshop.io/admin/api/series/1/products/482")
req = Net::HTTP::Delete.new(uri)
req["Authorization"] = "Bearer zspat_..."
resp = Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) { |http| http.request(req) }
# 204 No Content
We value your privacy
We use cookies for essential site functionality and, with your consent, analytics to understand how our platform is used. No personal data is shared with third parties. See our Privacy Policy for details.