# Catégories

Les catégories regroupent les produits d'une boutique. Elles ont deux niveaux : une catégorie principale peut contenir des sous-catégories, et une sous-catégorie n'en contient aucune. Ces six endpoints listent, lisent, créent, modifient, suppriment et réordonnent les catégories.

Un produit rejoint une catégorie par son champ `category_id`, voir [Produits](https://dzbuild.com/fr/fr/api-docs/resources/products.md). Une section `category-products` de la page d'accueil prend aussi un id de catégorie, voir [Sections de la page d'accueil](https://dzbuild.com/fr/fr/api-docs/resources/home-layout.md).

## Avant de commencer[​](#avant-de-commencer "Lien direct vers Avant de commencer")

* Les lectures demandent `products:read` et les écritures `products:write`, les mêmes scopes que pour les produits. Les clés marchand ont les deux.
* `POST`, `PATCH` et `DELETE` demandent une `Idempotency-Key`. Voir [Idempotence](https://dzbuild.com/fr/fr/api-docs/idempotency.md).
* L'image de la catégorie s'ajoute dans le dashboard. L'API renvoie son URL mais ne peut ni la téléverser ni la changer.

## L'objet catégorie[​](#lobjet-catégorie "Lien direct vers L'objet catégorie")

| Champ                | Type           | Notes                                                                                                                                                                                                                                                                                                                                                                                                                                  |
| -------------------- | -------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `id`                 | int            | L'id de la catégorie.                                                                                                                                                                                                                                                                                                                                                                                                                  |
| `name`               | string         | 1 à 100 caractères.                                                                                                                                                                                                                                                                                                                                                                                                                    |
| `slug`               | string         | Tiré du nom et unique dans la boutique. La page de la catégorie dans la boutique l'utilise dans son adresse.                                                                                                                                                                                                                                                                                                                           |
| `description`        | string ou null | Texte libre.                                                                                                                                                                                                                                                                                                                                                                                                                           |
| `image`              | string ou null | URL complète de l'image, ou `null`.                                                                                                                                                                                                                                                                                                                                                                                                    |
| `parent_id`          | int ou null    | La catégorie parente, ou `null` pour une catégorie principale.                                                                                                                                                                                                                                                                                                                                                                         |
| `show_subcategories` | bool           | Affiche les sous-catégories en vignettes sur la page de la catégorie dans la boutique (**Afficher la section des sous-catégories** dans le formulaire de la catégorie). Enregistré à `true` pour une sous-catégorie. Tant que **Sous-catégories uniquement dans la catégorie parente** est activé (`/dashboard/categories`), la boutique affiche les vignettes sur la page de chaque catégorie, quelle que soit la valeur de ce champ. |
| `sort_order`         | int            | Position dans les listes de catégories de la boutique, la plus petite d'abord.                                                                                                                                                                                                                                                                                                                                                         |
| `status`             | string         | `active` ou `inactive`.                                                                                                                                                                                                                                                                                                                                                                                                                |
| `created_at`         | string         | `YYYY-MM-DD HH:MM:SS`, heure du serveur.                                                                                                                                                                                                                                                                                                                                                                                               |

La liste et la lecture unitaire ajoutent `product_count`, le nombre de produits dans la catégorie. La lecture unitaire ajoute aussi `children`, ses sous-catégories.

## `GET /v1/categories`[​](#get-v1categories "Lien direct vers get-v1categories")

Les catégories de la boutique, de la plus récente à la plus ancienne, en liste à curseur. Triez-les par `sort_order` pour obtenir l'ordre de la boutique.

**Auth :** clé plateforme avec `products:read`.

### Paramètres de requête[​](#paramètres-de-requête "Lien direct vers Paramètres de requête")

| Param       | Type                   | Défaut | Notes                                                                                                                                                            |
| ----------- | ---------------------- | ------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `parent_id` | id, `0` ou `null`      | aucun  | Un id de catégorie renvoie ses sous-catégories. `0`, `null` ou une valeur vide renvoie les catégories principales. Toute autre valeur renvoie `400 bad_request`. |
| `status`    | `active` ou `inactive` | aucun  | Seulement les catégories qui ont ce statut. Toute autre valeur est ignorée.                                                                                      |
| `limit`     | int                    | 50     | 1 à 200.                                                                                                                                                         |
| `cursor`    | string                 | aucun  | Le `next_cursor` de la page précédente. Voir [Pagination](https://dzbuild.com/fr/fr/api-docs/pagination.md).                                                     |

### Requête[​](#requête "Lien direct vers Requête")

```
curl 'https://api.dzbuild.app/v1/categories?parent_id=0' \

  -H "Authorization: Bearer $DZ_KEY"
```

### Réponse 200[​](#réponse-200 "Lien direct vers Réponse 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}`[​](#get-v1categoriesid "Lien direct vers get-v1categoriesid")

Une catégorie avec `product_count` et `children`, ses sous-catégories triées par `sort_order`.

**Auth :** clé plateforme avec `products:read`.

Un id qui n'est pas fait que de chiffres répond `400 bad_request`. Une catégorie d'une autre boutique répond `404 not_found`, comme une catégorie qui n'existe pas.

### Requête[​](#requête-1 "Lien direct vers Requête")

```
curl 'https://api.dzbuild.app/v1/categories/10' \

  -H "Authorization: Bearer $DZ_KEY"
```

### Réponse 200[​](#réponse-200-1 "Lien direct vers Réponse 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`[​](#post-v1categories "Lien direct vers post-v1categories")

Crée une catégorie et la place en dernier : son `sort_order` vaut un de plus que le plus grand de la boutique.

**Auth :** clé plateforme avec `products:write`. **Nécessite `Idempotency-Key`.**

### Corps[​](#corps "Lien direct vers Corps")

| Champ                | Type                   | Requis | Notes                                                                                                                                                       |
| -------------------- | ---------------------- | ------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `name`               | string                 | oui    | Espaces de début et de fin retirés, 1 à 100 caractères.                                                                                                     |
| `description`        | string ou null         | non    | Espaces de début et de fin retirés. Une chaîne vide est enregistrée comme `null`.                                                                           |
| `parent_id`          | int ou null            | non    | Une catégorie principale de cette boutique : la nouvelle catégorie devient sa sous-catégorie. `null` ou `0` crée une catégorie principale.                  |
| `status`             | `active` ou `inactive` | non    | `active` par défaut.                                                                                                                                        |
| `show_subcategories` | bool                   | non    | `true` par défaut. Ignoré, et enregistré à `true`, pour une sous-catégorie ou tant que **Sous-catégories uniquement dans la catégorie parente** est activé. |

Le `slug` est tiré du nom : en minuscules, lettres et chiffres gardés, et chaque suite d'autres caractères remplacée par un seul `-`. Un nom en arabe garde ses lettres arabes. Quand une autre catégorie de la boutique a déjà ce `slug`, `-2`, `-3` et ainsi de suite est ajouté.

### Requête[​](#requête-2 "Lien direct vers Requête")

```
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}'
```

### Réponse 201[​](#réponse-201 "Lien direct vers Réponse 201")

La réponse est l'objet catégorie, sans `product_count` ni `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}`[​](#patch-v1categoriesid "Lien direct vers patch-v1categoriesid")

Ne change que les champs envoyés. Le corps accepte les champs de `POST`, tous facultatifs.

**Auth :** clé plateforme avec `products:write`. **Nécessite `Idempotency-Key`.**

* Un nouveau `name` donne un nouveau `slug` : l'adresse de la page de la catégorie dans la boutique change et les liens vers l'ancienne adresse ne marchent plus. Renvoyer le même nom garde le `slug`.
* `parent_id` déplace la catégorie. Un id de catégorie principale en fait une sous-catégorie, et `null` ou `0` en fait une catégorie principale. Une catégorie qui a des sous-catégories ne peut pas devenir une sous-catégorie, et une catégorie ne peut pas être sa propre parente.
* Un corps vide ne change rien et renvoie la catégorie telle quelle.

### Requête[​](#requête-3 "Lien direct vers Requête")

```
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"}'
```

### Réponse 200[​](#réponse-200-2 "Lien direct vers Réponse 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}`[​](#delete-v1categoriesid "Lien direct vers delete-v1categoriesid")

Supprime une catégorie vide et son image.

**Auth :** clé plateforme avec `products:write`. **Nécessite `Idempotency-Key`.**

Une catégorie qui contient encore des produits ou des sous-catégories répond `409 category_not_empty`, et rien n'est supprimé. Le message d'erreur donne les nombres. Pour vider la catégorie :

* Sous-catégories : passez chacune en catégorie principale avec `PATCH` et `"parent_id": null`, ou supprimez-la d'abord.
* Produits : l'API ne peut pas retirer un produit d'une catégorie. Donner un `category_id` à un produit ajoute une catégorie et garde celles qu'il a déjà, voir [Produits](https://dzbuild.com/fr/fr/api-docs/resources/products.md). Changez les catégories de ces produits dans le dashboard.

### Requête[​](#requête-4 "Lien direct vers Requête")

```
curl -X DELETE 'https://api.dzbuild.app/v1/categories/14' \

  -H "Authorization: Bearer $DZ_KEY" \

  -H "Idempotency-Key: cat-14-delete-1"
```

### Réponse 200[​](#réponse-200-3 "Lien direct vers Réponse 200")

```
{

  "data": {

    "deleted": true,

    "id": 14

  }

}
```

## `POST /v1/categories/reorder`[​](#post-v1categoriesreorder "Lien direct vers post-v1categoriesreorder")

Fixe le `sort_order` des catégories listées, en une seule fois : soit elles changent toutes, soit aucune ne change.

**Auth :** clé plateforme avec `products:write`. **Nécessite `Idempotency-Key`.**

### Corps[​](#corps-1 "Lien direct vers Corps")

| Champ        | Type  | Requis | Notes                                                                                                                           |
| ------------ | ----- | ------ | ------------------------------------------------------------------------------------------------------------------------------- |
| `categories` | array | oui    | Les catégories dans leur nouvel ordre. Chaque élément est `{"id": 10}`, `{"id": 10, "sort_order": 7}` ou un id seul comme `10`. |

* Un élément sans `sort_order` prend sa position dans le tableau : 1, 2, 3 et ainsi de suite. Un élément avec `sort_order` prend ce nombre.
* Chaque id doit appartenir à la boutique et n'apparaître qu'une fois.
* Les catégories que vous ne listez pas gardent leur `sort_order`.
* Un réordonnancement n'est pas enregistré dans l'historique des changements, il ne peut donc pas être annulé. Lisez d'abord la liste si vous risquez de vouloir revenir à l'ancien ordre.

### Requête[​](#requête-5 "Lien direct vers Requête")

```
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}]}'
```

### Réponse 200[​](#réponse-200-4 "Lien direct vers Réponse 200")

```
{

  "data": {

    "reordered": 2,

    "categories": [

      {"id": 14, "sort_order": 1},

      {"id": 10, "sort_order": 2}

    ]

  }

}
```

## Annulation[​](#annulation "Lien direct vers Annulation")

`POST`, `PATCH` et `DELETE` sont enregistrés dans l'historique des changements de la boutique. Leur réponse ne contient pas l'id du changement : `GET /v1/changes?entity=category` liste les changements des catégories, du plus récent au plus ancien, avec `store:read`. `entity_id` est l'id de la catégorie.

### Requête[​](#requête-6 "Lien direct vers Requête")

```
curl 'https://api.dzbuild.app/v1/changes?entity=category&limit=1' \

  -H "Authorization: Bearer $DZ_KEY"
```

### Réponse 200[​](#réponse-200-5 "Lien direct vers Réponse 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` annule un changement. Il demande `products:write` et une `Idempotency-Key`.

* Annuler un `PATCH` remet les valeurs d'avant des champs que cet appel a changés, avec les mêmes contrôles que `PATCH`.
* Annuler un `DELETE` recrée la catégorie sous son ancien id, sans son image. Si son ancienne catégorie parente n'existe plus ou est devenue une sous-catégorie, l'annulation répond `422 invalid_parent`.
* Une création ne s'annule pas : l'annulation répond `422 nothing_to_restore`. Supprimez plutôt la catégorie.
* Quand la catégorie n'existe plus, ou que son ancien id est pris, l'annulation répond `422 restore_target_missing`.
* Un changement annulé une deuxième fois répond `409 already_undone`.
* Les changements faits dans le dashboard ne sont pas enregistrés, on ne peut donc pas les annuler par l'API.
* Le jeton d'une application installée ne peut ni lire ni annuler les changements : les deux répondent `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

  }

}
```

## Erreurs[​](#erreurs "Lien direct vers Erreurs")

| HTTP | Code                     | Cause                                                                                                                                                                                                                                                                                                                                       |
| ---- | ------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| 400  | `bad_request`            | L'id dans le chemin n'est pas fait que de chiffres, le corps n'est pas un JSON valide, le filtre `parent_id` n'est ni un id, ni `0`, ni `null`, ni vide, `categories` manque ou n'est pas un tableau, ou `Idempotency-Key` manque ou est mal formée.                                                                                        |
| 401  | `unauthorized`           | Clé absente ou invalide.                                                                                                                                                                                                                                                                                                                    |
| 402  | `quota_exceeded`         | Le quota mensuel de requêtes de la boutique est épuisé. Voir [Limites de taux](https://dzbuild.com/fr/fr/api-docs/rate-limits.md).                                                                                                                                                                                                          |
| 403  | `forbidden`              | `Missing scope: products:read`, `Missing scope: products:write` ou `Missing scope: store:read`, ou `API access requires an active Enterprise plan` pour une clé du marchand dont la boutique n'a pas de plan Enterprise actif, ou `Apps cannot use this endpoint` quand le jeton d'une application installée lit ou annule des changements. |
| 404  | `not_found`              | Aucune catégorie avec cet id dans la boutique, un réordonnancement liste un id qui n'est pas dans la boutique, ou une annulation vise un changement absent de l'historique de la boutique.                                                                                                                                                  |
| 409  | `category_not_empty`     | La catégorie contient encore des produits ou des sous-catégories.                                                                                                                                                                                                                                                                           |
| 409  | `already_undone`         | Annulation seulement : le changement a déjà été annulé.                                                                                                                                                                                                                                                                                     |
| 413  | `payload_too_large`      | Le corps dépasse 1 Mo.                                                                                                                                                                                                                                                                                                                      |
| 422  | `validation_error`       | `name` manque, est vide ou dépasse 100 caractères, `status` n'est ni `active` ni `inactive`, `parent_id` n'est pas un id, ou un réordonnancement est vide, répète un id, contient un id qui n'est pas un entier positif ou un `sort_order` qui n'est pas un entier.                                                                         |
| 422  | `invalid_parent`         | `parent_id` n'est pas une catégorie de cette boutique, est lui-même une sous-catégorie, est la catégorie elle-même, ou la catégorie a des sous-catégories et ne peut pas passer sous une autre.                                                                                                                                             |
| 422  | `nothing_to_restore`     | Annulation seulement : le changement a créé la catégorie.                                                                                                                                                                                                                                                                                   |
| 422  | `restore_target_missing` | Annulation seulement : la catégorie n'existe plus, ou son ancien id est pris.                                                                                                                                                                                                                                                               |
| 422  | `idempotency_key_reuse`  | La même `Idempotency-Key` a servi avec un autre corps.                                                                                                                                                                                                                                                                                      |
| 429  | `rate_limited`           | Trop d'appels dans la minute en cours. Attendez les secondes indiquées par `Retry-After`. Voir [Limites de taux](https://dzbuild.com/fr/fr/api-docs/rate-limits.md).                                                                                                                                                                        |
| 500  | `server_error`           | La requête a échoué. Réessayez avec la même `Idempotency-Key`.                                                                                                                                                                                                                                                                              |
