Categories

Organize products into categories with facets and ordering

GET /admin/api/categories

List all categories as a flat list ordered for tree rendering. Top-level categories come first, followed by children.

POST /admin/api/categories

Create a new category. A URL slug is auto-derived from the name. Supports optional parent_id for nesting and content translations.

PUT /admin/api/categories/{id}

Update a category's name, parent, status, and translations. Re-derives the slug if the name changes. Rejects moves that would create a cycle.

DELETE /admin/api/categories/{id}

Delete a category. Returns 409 Conflict if products are currently assigned to it.

PATCH /admin/api/categories/{id}/status

Toggle a category's active/inactive status. Returns the updated category.

PUT /admin/api/categories/reorder

Reorder categories within a single sibling group. Provide the parent_id (or null for top-level) and the complete ordered list of category IDs.

GET /admin/api/categories/{id}/facets

Get the option IDs whitelisted as storefront filters for this category.

PUT /admin/api/categories/{id}/facets

Replace the facet option whitelist for a category. Send an empty array to disable all storefront filters.

POST /admin/api/categories/{id}/image

Upload or replace the category's hero image. Accepts multipart/form-data with a single field named "file". Images are resized to max 1200x1200 px and converted to WebP. Replaces any existing image.

DELETE /admin/api/categories/{id}/image

Remove the category's hero image. Idempotent — succeeds even if no image is set.

GET /admin/api/categories/export.csv

Stream the entire category tree as a UTF-8 CSV download (BOM-prefixed for Excel). Columns: id, parent_id, name, full_path (breadcrumb joined with " > "), and url (storefront path). Both active and inactive categories are included; there are no filters.

GET /admin/api/categories/import/example.csv

Download a small example CSV the import modal offers as a starting template. Contains three illustrative rows (a top-level category, a child, and a grandchild) with the import columns name, parent_path, status.

POST /admin/api/categories/import/preview

Upload a category CSV (multipart/form-data, single "file" field) to validate it in memory without writing anything. Returns a summary, the list of rows that would be created, and any per-row errors. Recognized columns are name (required), parent_path, and status; unknown columns (e.g. id, url from an export) are ignored. Parents are matched by full_path against existing categories and rows declared earlier in the same file.

POST /admin/api/categories/import/confirm

Upload a category CSV (multipart/form-data, single "file" field) and apply it. Re-parses and re-validates the file, then inserts every planned category inside a single transaction. By default any validation error aborts the whole import and returns 422 with a preview-shaped body. Pass ?skip_invalid_rows=true to import the valid rows and report the rest in skipped_rows.

GET /admin/api/categories

List all categories as a flat list ordered for tree rendering. Top-level categories come first, followed by children.

Response 200

{
  "categories": [
    {
      "id": 1,
      "slug": "clothing",
      "name": "Clothing",
      "parent_id": null,
      "sort_order": 0,
      "status": "active"
    },
    {
      "id": 2,
      "slug": "t-shirts",
      "name": "T-Shirts",
      "parent_id": 1,
      "sort_order": 0,
      "status": "active"
    },
    {
      "id": 3,
      "slug": "accessories",
      "name": "Accessories",
      "parent_id": null,
      "sort_order": 1,
      "status": "inactive"
    }
  ]
}

Example

curl "https://yourshop.zeroshop.io/admin/api/categories" \
  -H "Authorization: Bearer zspat_..."
POST /admin/api/categories

Create a new category. A URL slug is auto-derived from the name. Supports optional parent_id for nesting and content translations.

Request Body

{
  "name": "T-Shirts",
  "parent_id": 1,
  "status": "active",
  "translations": [
    {
      "locale": "de",
      "field": "name",
      "value": "T-Shirts"
    }
  ]
}

Response 201

{
  "id": 2,
  "slug": "t-shirts",
  "name": "T-Shirts",
  "parent_id": 1,
  "sort_order": 0,
  "status": "active",
  "translations": {
    "de": {
      "name": "T-Shirts"
    }
  }
}

Example

curl -X POST "https://yourshop.zeroshop.io/admin/api/categories" \
  -H "Authorization: Bearer zspat_..." \
  -H "Content-Type: application/json" \
  -d '{
    "name": "T-Shirts",
    "parent_id": 1,
    "status": "active",
    "translations": [
      { "locale": "de", "field": "name", "value": "T-Shirts" }
    ]
  }'
PUT /admin/api/categories/{id}

Update a category's name, parent, status, and translations. Re-derives the slug if the name changes. Rejects moves that would create a cycle.

Request Body

{
  "name": "T-Shirts & Tops",
  "parent_id": 1,
  "status": "active",
  "translations": [
    {
      "locale": "de",
      "field": "name",
      "value": "T-Shirts & Oberteile"
    }
  ]
}

Response 200

{
  "id": 2,
  "slug": "t-shirts-tops",
  "name": "T-Shirts & Tops",
  "parent_id": 1,
  "sort_order": 0,
  "status": "active",
  "translations": {
    "de": {
      "name": "T-Shirts & Oberteile"
    }
  }
}

Example

curl -X PUT "https://yourshop.zeroshop.io/admin/api/categories/2" \
  -H "Authorization: Bearer zspat_..." \
  -H "Content-Type: application/json" \
  -d '{
    "name": "T-Shirts & Tops",
    "parent_id": 1,
    "status": "active",
    "translations": [
      { "locale": "de", "field": "name", "value": "T-Shirts & Oberteile" }
    ]
  }'
DELETE /admin/api/categories/{id}

Delete a category. Returns 409 Conflict if products are currently assigned to it.

Response 204

Example

curl -X DELETE "https://yourshop.zeroshop.io/admin/api/categories/3" \
  -H "Authorization: Bearer zspat_..."
PATCH /admin/api/categories/{id}/status

Toggle a category's active/inactive status. Returns the updated category.

Request Body

{
  "status": "inactive"
}

Response 200

{
  "id": 2,
  "slug": "t-shirts",
  "name": "T-Shirts",
  "parent_id": 1,
  "sort_order": 0,
  "status": "inactive"
}

Example

curl -X PATCH "https://yourshop.zeroshop.io/admin/api/categories/2/status" \
  -H "Authorization: Bearer zspat_..." \
  -H "Content-Type: application/json" \
  -d '{ "status": "inactive" }'
PUT /admin/api/categories/reorder

Reorder categories within a single sibling group. Provide the parent_id (or null for top-level) and the complete ordered list of category IDs.

Request Body

{
  "parent_id": null,
  "ordered_ids": [
    1,
    3
  ]
}

Response 204

Example

curl -X PUT "https://yourshop.zeroshop.io/admin/api/categories/reorder" \
  -H "Authorization: Bearer zspat_..." \
  -H "Content-Type: application/json" \
  -d '{ "parent_id": null, "ordered_ids": [1, 3] }'
GET /admin/api/categories/{id}/facets

Get the option IDs whitelisted as storefront filters for this category.

Response 200

{
  "option_ids": [
    1,
    3,
    5
  ]
}

Example

curl "https://yourshop.zeroshop.io/admin/api/categories/1/facets" \
  -H "Authorization: Bearer zspat_..."
PUT /admin/api/categories/{id}/facets

Replace the facet option whitelist for a category. Send an empty array to disable all storefront filters.

Request Body

{
  "option_ids": [
    1,
    3,
    5
  ]
}

Response 204

Example

curl -X PUT "https://yourshop.zeroshop.io/admin/api/categories/1/facets" \
  -H "Authorization: Bearer zspat_..." \
  -H "Content-Type: application/json" \
  -d '{ "option_ids": [1, 3, 5] }'
POST /admin/api/categories/{id}/image

Upload or replace the category's hero image. Accepts multipart/form-data with a single field named "file". Images are resized to max 1200x1200 px and converted to WebP. Replaces any existing image.

Parameters

Name Type Required Description
file file required The image file (JPEG, PNG, GIF, or WebP).

Response 200

{
  "image_url": "https://cdn.zeroshop.io/tenants/abc/categories/7/7a3f1e.webp"
}

Example

curl -X POST "https://yourshop.zeroshop.io/admin/api/categories/7/image" \
  -H "Authorization: Bearer zspat_..." \
  -F "file=@hero.jpg"
DELETE /admin/api/categories/{id}/image

Remove the category's hero image. Idempotent — succeeds even if no image is set.

Response 204

Example

curl -X DELETE "https://yourshop.zeroshop.io/admin/api/categories/7/image" \
  -H "Authorization: Bearer zspat_..."
GET /admin/api/categories/export.csv

Stream the entire category tree as a UTF-8 CSV download (BOM-prefixed for Excel). Columns: id, parent_id, name, full_path (breadcrumb joined with " > "), and url (storefront path). Both active and inactive categories are included; there are no filters.

Response 200

"id,parent_id,name,full_path,url\r\n1,,Men,Men,/c/men\r\n2,1,Apparel,Men > Apparel,/c/men/apparel\r\n3,2,T-Shirts,Men > Apparel > T-Shirts,/c/men/apparel/t-shirts\r\n"

Example

curl "https://yourshop.zeroshop.io/admin/api/categories/export.csv" \
  -H "Authorization: Bearer zspat_..." \
  -o categories.csv
GET /admin/api/categories/import/example.csv

Download a small example CSV the import modal offers as a starting template. Contains three illustrative rows (a top-level category, a child, and a grandchild) with the import columns name, parent_path, status.

Response 200

"name,parent_path,status\r\nMen,,active\r\nApparel,Men,active\r\nT-Shirts,Men > Apparel,active\r\n"

Example

curl "https://yourshop.zeroshop.io/admin/api/categories/import/example.csv" \
  -H "Authorization: Bearer zspat_..." \
  -o categories-example.csv
POST /admin/api/categories/import/preview

Upload a category CSV (multipart/form-data, single "file" field) to validate it in memory without writing anything. Returns a summary, the list of rows that would be created, and any per-row errors. Recognized columns are name (required), parent_path, and status; unknown columns (e.g. id, url from an export) are ignored. Parents are matched by full_path against existing categories and rows declared earlier in the same file.

Parameters

Name Type Required Description
file file required The CSV file. Max 2 MB. Columns: name (required), parent_path (optional, e.g. "Men > Apparel"), status (optional, "active" or "inactive", defaults to "active").

Response 200

{
  "summary": {
    "total_rows": 3,
    "to_create": 2,
    "errors": 1,
    "skippable_error_rows": 1
  },
  "changes": [
    {
      "row": 2,
      "name": "Apparel",
      "parent_path": "Men",
      "status": "active"
    },
    {
      "row": 3,
      "name": "T-Shirts",
      "parent_path": "Men > Apparel",
      "status": "active"
    }
  ],
  "errors": [
    {
      "row": 4,
      "code": "PARENT_NOT_FOUND",
      "message": "parent path \"Nope\" does not exist and is not created earlier in the CSV"
    }
  ],
  "can_confirm": false
}

Example

curl -X POST "https://yourshop.zeroshop.io/admin/api/categories/import/preview" \
  -H "Authorization: Bearer zspat_..." \
  -F "file=@categories.csv"
POST /admin/api/categories/import/confirm

Upload a category CSV (multipart/form-data, single "file" field) and apply it. Re-parses and re-validates the file, then inserts every planned category inside a single transaction. By default any validation error aborts the whole import and returns 422 with a preview-shaped body. Pass ?skip_invalid_rows=true to import the valid rows and report the rest in skipped_rows.

Parameters

Name Type Required Description
skip_invalid_rows boolean optional When true, rows with errors are skipped and the valid rows are still imported. When false (default), any error aborts the import with a 422.
file file required The CSV file. Same format as the preview endpoint. Max 2 MB.

Response 200

{
  "created": 2,
  "skipped": 0,
  "skipped_rows": []
}

Example

curl -X POST "https://yourshop.zeroshop.io/admin/api/categories/import/confirm?skip_invalid_rows=true" \
  -H "Authorization: Bearer zspat_..." \
  -F "file=@categories.csv"

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.