Categories
Categories group the products of a store. They are two levels deep: a top-level category can hold subcategories, and a subcategory cannot hold any. These six endpoints list, read, create, change, delete and reorder them.
A product joins a category through its category_id field, see Products. A category-products section of the home page takes a category id too, see Home page sections.
Before you start
- Reads need
products:readand writes needproducts:write, the same scopes as products. Merchant keys carry both. POST,PATCHandDELETEneed anIdempotency-Key. See Idempotency.- The category image is added in the dashboard. The API returns its URL but cannot upload or change it.
The category object
| Field | Type | Notes |
|---|---|---|
id | int | The category id. |
name | string | 1 to 100 characters. |
slug | string | Made from the name and unique in the store. The category's page on the store uses it in its address. |
description | string or null | Free text. |
image | string or null | Full URL of the image, or null. |
parent_id | int or null | The parent category, or null for a top-level category. |
show_subcategories | bool | Shows the subcategories as tiles on the category's page in the store (Show subcategories section on the category form). Stored as true for a subcategory. While Subcategories only inside the parent category is on (/dashboard/categories), the store shows the tiles on every category's page whatever this field says. |
sort_order | int | Position in the store's category lists, lowest first. |
status | string | active or inactive. |
created_at | string | YYYY-MM-DD HH:MM:SS, server time. |
The list and the single read add product_count, the number of products in the category. The single read also adds children, its subcategories.
GET /v1/categories
The store's categories, newest first, as a cursor list. Sort them by sort_order to get the store's order.
Auth: platform key with products:read.
Query parameters
| Param | Type | Default | Notes |
|---|---|---|---|
parent_id | id, 0 or null | none | A category id returns its subcategories. 0, null or an empty value returns the top-level categories. Anything else returns 400 bad_request. |
status | active or inactive | none | Only the categories with this status. Any other value is ignored. |
limit | int | 50 | 1 to 200. |
cursor | string | none | next_cursor of the previous page. See Pagination. |
Request
curl 'https://api.dzbuild.app/v1/categories?parent_id=0' \
-H "Authorization: Bearer $DZ_KEY"
Response 200
{
"data": {
"items": [
{
"id": 14,
"name": "Montres",
"slug": "montres",
"description": null,
"image": null,
"parent_id": null,
"show_subcategories": true,
"sort_order": 4,
"status": "active",
"created_at": "2026-09-30 11:20:05",
"product_count": 0
},
{
"id": 10,
"name": "Parfums",
"slug": "parfums",
"description": "Eaux de parfum et coffrets",
"image": null,
"parent_id": null,
"show_subcategories": true,
"sort_order": 1,
"status": "active",
"created_at": "2026-09-12 09:41:37",
"product_count": 18
}
],
"next_cursor": null,
"has_more": false
}
}
GET /v1/categories/{id}
One category with product_count and children, its subcategories ordered by sort_order.
Auth: platform key with products:read.
An id that is not all digits answers 400 bad_request. A category of another store answers 404 not_found, like one that does not exist.
Request
curl 'https://api.dzbuild.app/v1/categories/10' \
-H "Authorization: Bearer $DZ_KEY"
Response 200
{
"data": {
"id": 10,
"name": "Parfums",
"slug": "parfums",
"description": "Eaux de parfum et coffrets",
"image": null,
"parent_id": null,
"show_subcategories": true,
"sort_order": 1,
"status": "active",
"created_at": "2026-09-12 09:41:37",
"children": [
{"id": 11, "name": "Parfums femme", "slug": "parfums-femme", "sort_order": 2, "status": "active"},
{"id": 12, "name": "Parfums homme", "slug": "parfums-homme", "sort_order": 3, "status": "active"}
],
"product_count": 18
}
}
POST /v1/categories
Creates a category and places it last: its sort_order is one more than the highest in the store.
Auth: platform key with products:write. Requires Idempotency-Key.
Body
| Field | Type | Required | Notes |
|---|---|---|---|
name | string | yes | Trimmed, 1 to 100 characters. |
description | string or null | no | Trimmed. An empty string is stored as null. |
parent_id | int or null | no | A top-level category of this store: the new category becomes its subcategory. null or 0 makes a top-level category. |
status | active or inactive | no | Defaults to active. |
show_subcategories | bool | no | Defaults to true. Ignored, and stored as true, for a subcategory or while Subcategories only inside the parent category is on. |
The slug is made from the name: lowercase, letters and digits kept, and any run of other characters turned into one -. An Arabic name keeps its Arabic letters. When another category of the store already has that slug, -2, -3 and so on is added.
Request
curl -X POST 'https://api.dzbuild.app/v1/categories' \
-H "Authorization: Bearer $DZ_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: cat-create-coffrets-1" \
-d '{"name": "Coffrets cadeaux", "parent_id": 10}'
Response 201
The answer is the category object, without product_count and children.
{
"data": {
"id": 15,
"name": "Coffrets cadeaux",
"slug": "coffrets-cadeaux",
"description": null,
"image": null,
"parent_id": 10,
"show_subcategories": true,
"sort_order": 5,
"status": "active",
"created_at": "2026-10-06 14:02:11"
}
}
PATCH /v1/categories/{id}
Changes only the fields you send. The body takes the fields of POST, all optional.
Auth: platform key with products:write. Requires Idempotency-Key.
- A new
namegives a newslug, so the address of the category's page on the store changes and links to the old address stop working. Sending the same name keeps theslug. parent_idmoves the category. A top-level category id makes it a subcategory, andnullor0makes it top-level. A category that has subcategories cannot become a subcategory, and a category cannot be its own parent.- An empty body changes nothing and answers the category as it is.
Request
curl -X PATCH 'https://api.dzbuild.app/v1/categories/15' \
-H "Authorization: Bearer $DZ_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: cat-15-move-1" \
-d '{"parent_id": null, "status": "inactive"}'
Response 200
{
"data": {
"id": 15,
"name": "Coffrets cadeaux",
"slug": "coffrets-cadeaux",
"description": null,
"image": null,
"parent_id": null,
"show_subcategories": true,
"sort_order": 5,
"status": "inactive",
"created_at": "2026-10-06 14:02:11"
}
}
DELETE /v1/categories/{id}
Deletes an empty category and its image.
Auth: platform key with products:write. Requires Idempotency-Key.
A category that still holds products or subcategories answers 409 category_not_empty, and nothing is deleted. The error message gives the counts. To empty the category:
- Subcategories: move each one to the top level with
PATCHand"parent_id": null, or delete it first. - Products: the API cannot take a product out of a category. Setting a product's
category_idadds a category and keeps the ones the product already has, see Products. Change the categories of those products in the dashboard.
Request
curl -X DELETE 'https://api.dzbuild.app/v1/categories/14' \
-H "Authorization: Bearer $DZ_KEY" \
-H "Idempotency-Key: cat-14-delete-1"
Response 200
{
"data": {
"deleted": true,
"id": 14
}
}
POST /v1/categories/reorder
Sets the sort_order of the categories you list, all in one step: either every one of them changes or none does.
Auth: platform key with products:write. Requires Idempotency-Key.
Body
| Field | Type | Required | Notes |
|---|---|---|---|
categories | array | yes | The categories in their new order. Each item is {"id": 10}, {"id": 10, "sort_order": 7} or a bare id such as 10. |
- An item without
sort_ordergets its position in the array: 1, 2, 3 and so on. An item withsort_ordergets that number. - Each id must belong to the store and appear once.
- The categories you leave out keep their
sort_order. - A reorder is not recorded in the change history, so it cannot be undone. Read the list first if you may want the old order back.
Request
curl -X POST 'https://api.dzbuild.app/v1/categories/reorder' \
-H "Authorization: Bearer $DZ_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: cat-reorder-1" \
-d '{"categories": [{"id": 14}, {"id": 10}]}'
Response 200
{
"data": {
"reordered": 2,
"categories": [
{"id": 14, "sort_order": 1},
{"id": 10, "sort_order": 2}
]
}
}
Undo
POST, PATCH and DELETE are recorded in the store's change history. Their answer does not carry the change id: GET /v1/changes?entity=category lists the category changes, newest first, with store:read. entity_id is the category id.
Request
curl 'https://api.dzbuild.app/v1/changes?entity=category&limit=1' \
-H "Authorization: Bearer $DZ_KEY"
Response 200
{
"data": {
"items": [
{
"id": 120,
"entity": "category",
"entity_id": "15",
"action": "update",
"summary": "Updated category #15 (parent_id, status, show_subcategories)",
"undone_at": null,
"created_at": "2026-10-06 14:05:48",
"undone": false
}
],
"next_cursor": "MTIw",
"has_more": true
}
}
POST /v1/changes/{id}/undo reverts one change. It needs products:write and an Idempotency-Key.
- Undoing a
PATCHputs back the earlier values of the fields that call changed, with the same checks asPATCH. - Undoing a
DELETEcreates the category again under its old id, without its image. When its old parent is gone or has become a subcategory, the undo answers422 invalid_parent. - A create cannot be undone: the undo answers
422 nothing_to_restore. Delete the category instead. - When the category is gone, or its old id is taken, the undo answers
422 restore_target_missing. - A change undone a second time answers
409 already_undone. - Changes made in the dashboard are not recorded, so they cannot be undone through the API.
- An installed app's token cannot read or undo changes: both answer
403 forbidden(Apps cannot use this endpoint).
curl -X POST 'https://api.dzbuild.app/v1/changes/120/undo' \
-H "Authorization: Bearer $DZ_KEY" \
-H "Idempotency-Key: undo-120"
{
"data": {
"undone": true,
"change_id": 120,
"entity": "category",
"undo_change_id": 121
}
}
Errors
| HTTP | Code | Cause |
|---|---|---|
| 400 | bad_request | The id in the path is not all digits, the body is not valid JSON, the parent_id filter is not an id, 0, null or empty, categories is missing or not an array, or Idempotency-Key is missing or malformed. |
| 401 | unauthorized | Bad or missing key. |
| 402 | quota_exceeded | The store's monthly request quota is used up. See Rate limits. |
| 403 | forbidden | Missing scope: products:read, Missing scope: products:write or Missing scope: store:read, or API access requires an active Enterprise plan for a merchant key whose store is not on an active Enterprise plan, or Apps cannot use this endpoint when an installed app's token reads or undoes changes. |
| 404 | not_found | No category with this id in the store, a reorder lists an id that is not in the store, or an undo names a change that is not in the store's history. |
| 409 | category_not_empty | The category still holds products or subcategories. |
| 409 | already_undone | Undo only: the change was already undone. |
| 413 | payload_too_large | The body is over 1 MB. |
| 422 | validation_error | name is missing, empty or over 100 characters, status is not active or inactive, parent_id is not an id, or a reorder is empty, repeats an id, has an id that is not a positive whole number or a sort_order that is not a whole number. |
| 422 | invalid_parent | parent_id is not a category of this store, is itself a subcategory, is the category itself, or the category has subcategories and cannot move under another one. |
| 422 | nothing_to_restore | Undo only: the change created the category. |
| 422 | restore_target_missing | Undo only: the category is gone, or its old id is taken. |
| 422 | idempotency_key_reuse | The same Idempotency-Key was used with a different body. |
| 429 | rate_limited | Too many calls in the current minute. Wait for the Retry-After seconds. See Rate limits. |
| 500 | server_error | The request failed. Retry with the same Idempotency-Key. |