# Changements et annulation

La plupart des écritures de configuration faites par l'API sont enregistrées comme changements : réglages, design et thème de la boutique, sections de la page d'accueil, catégories, stock, codes promo, pixels, tarifs et réglages de livraison, et sections de landing page. Chaque changement garde les valeurs qu'il a remplacées, et une annulation les réécrit en passant par les mêmes contrôles que l'écriture d'origine. Les écritures faites sur la boutique par Copilot, par un assistant IA connecté (Claude ou ChatGPT) ou par une application installée sont aussi enregistrées. Les changements enregistrés dans le dashboard ne le sont pas.

Les trois endpoints ci-dessous listent les changements, en lisent un en entier et en annulent un. Les écritures sous `/v1/store/home-layout` et la synchronisation des tarifs d'un transporteur répondent avec un `change_id`. Pour les autres écritures, retrouvez le changement avec `GET /v1/changes`.

## Ce qui est enregistré[​](#ce-qui-est-enregistré "Lien direct vers Ce qui est enregistré")

| `entity`              | Enregistré par                                                                               | `entity_id`                                                                  | Portée pour le lire en entier | Portée pour l'annuler |
| --------------------- | -------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------- | ----------------------------- | --------------------- |
| `store.settings`      | `PATCH /v1/store`                                                                            | `settings`                                                                   | `store:read`                  | `store:write`         |
| `store.design`        | `PATCH /v1/store/design`                                                                     | `design`                                                                     | `store:read`                  | `store:write`         |
| `store.theme`         | `POST /v1/store/theme`, `POST /v1/store/fast-checkout-theme`, `POST /v1/store/variant-style` | `theme`                                                                      | `store:read`                  | `store:write`         |
| `store.home_sections` | `PATCH /v1/store/home-sections`                                                              | `home_sections`                                                              | `store:read`                  | `store:write`         |
| `store.home_layout`   | Toute écriture sous `/v1/store/home-layout`                                                  | `layout`                                                                     | `store:read`                  | `store:write`         |
| `category`            | `POST /v1/categories`, `PATCH` et `DELETE /v1/categories/{id}`                               | Id de la catégorie                                                           | `products:read`               | `products:write`      |
| `stock`               | `POST /v1/products/{id}/stock`                                                               | Id du produit                                                                | `products:read`               | `products:write`      |
| `promo_code`          | `POST /v1/promo-codes`, `PATCH` et `DELETE /v1/promo-codes/{id}`                             | Id du code promo                                                             | `promos:read`                 | `promos:write`        |
| `pixels`              | `POST /v1/pixels`, `PATCH` et `DELETE /v1/pixels/{id}`                                       | L'id du pixel chez DZBuild, pas son `pixel_id`                               | `pixels:read`                 | `pixels:write`        |
| `shipping.rates`      | `POST /v1/shipping/rates`, `POST /v1/shipping/rates/sync`                                    | `rates`                                                                      | `shipping:read`               | `shipping:write`      |
| `shipping.settings`   | `PATCH /v1/shipping/settings`                                                                | `settings`                                                                   | `shipping:read`               | `shipping:write`      |
| `lp.section`          | Les écritures de sections sous `/v1/landing-pages/{id}/sections`                             | Id de la section, ou `lp:` suivi de l'id de la page pour un réordonnancement | `landing_pages:read`          | `landing_pages:write` |

* Ces écritures ne sont pas enregistrées et ne peuvent pas être annulées : les produits avec leurs images, variantes, offres, add-ons et règles de quantité (le stock est enregistré), les commandes, les landing pages elles-mêmes, l'ordre des catégories, les transporteurs, les webhooks et les clés.
* `lp.page` est accepté comme filtre `entity`, mais aucune écriture ne l'enregistre.
* L'enregistrement ne bloque pas une écriture. Quand un changement ne peut pas être enregistré, par exemple parce que ses valeurs d'avant ou d'après dépassent 256 KB, l'écriture passe quand même et ne peut pas être annulée. Les écritures de tarifs de livraison font exception : elles vérifient la taille d'abord et répondent `422 snapshot_too_large` au lieu de s'exécuter.
* Chaque portée du tableau fait partie des portées par défaut d'une nouvelle clé : une clé créée depuis le dashboard (**Paramètres → API**, `/dashboard/api`) peut donc utiliser les trois endpoints. Les portées sont figées à la création de la clé : une clé plus ancienne sans la portée qu'il faut répond `403 forbidden`. Créez une nouvelle clé depuis le dashboard.

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

Les changements de la boutique, du plus récent au plus ancien, sans les valeurs qu'ils ont remplacées.

**Auth :** clé plateforme avec `store:read`. Le jeton d'une application installée reçoit `403 forbidden` (`Apps cannot use this endpoint`).

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

| Param    | Type   | Défaut | Notes                                                                                                                      |
| -------- | ------ | ------ | -------------------------------------------------------------------------------------------------------------------------- |
| `entity` | string | aucun  | Seulement les changements d'une `entity` du tableau ci-dessus. Toute autre valeur est ignorée et la liste entière revient. |
| `limit`  | int    | 25     | De 1 à 100. Une valeur plus petite compte pour 1, une plus grande pour 100.                                                |
| `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/changes?limit=2' \

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

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

```
{

  "data": {

    "items": [

      {

        "id": 120,

        "entity": "shipping.rates",

        "entity_id": "rates",

        "action": "update",

        "summary": "Shipping rates updated for 2 wilaya(s)",

        "undone_at": null,

        "created_at": "2026-10-06 11:02:17",

        "undone": false

      },

      {

        "id": 119,

        "entity": "promo_code",

        "entity_id": "7",

        "action": "update",

        "summary": "Updated promo code SUMMER10",

        "undone_at": null,

        "created_at": "2026-10-06 10:52:30",

        "undone": false

      }

    ],

    "next_cursor": "MTE5",

    "has_more": true

  }

}
```

| Champ        | Signification                                                                                                    |
| ------------ | ---------------------------------------------------------------------------------------------------------------- |
| `id`         | L'id du changement, pour `GET /v1/changes/{id}` et l'annulation.                                                 |
| `entity`     | Ce qui a changé. Voir le tableau ci-dessus.                                                                      |
| `entity_id`  | L'élément qui a changé, en chaîne. Voir le tableau ci-dessus.                                                    |
| `action`     | `create`, `update` ou `delete`. Une annulation est enregistrée comme un `update`.                                |
| `summary`    | Une courte description en anglais. Une annulation affiche `Undo of change #` suivi de l'id du changement annulé. |
| `undone`     | `true` une fois le changement annulé.                                                                            |
| `undone_at`  | Quand le changement a été annulé, sinon `null`.                                                                  |
| `created_at` | `YYYY-MM-DD HH:MM:SS`, heure du serveur. `undone_at` suit le même format.                                        |

## `GET /v1/changes/{id}`[​](#get-v1changesid "Lien direct vers get-v1changesid")

Un changement avec `before`, les valeurs qu'il a remplacées, et `after`, les valeurs qu'il a écrites. Leur forme dépend de l'entité : certaines gardent seulement les champs touchés par l'écriture, d'autres l'élément entier.

**Auth :** clé plateforme avec `store:read`, plus la portée de lecture de l'entité du changement, d'après le tableau ci-dessus. Le jeton d'une application installée reçoit `403 forbidden`.

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

```
curl 'https://api.dzbuild.app/v1/changes/118' \

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

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

```
{

  "data": {

    "id": 118,

    "key_id": "dzpk_live_xxxxxxxxxxxxxx",

    "entity": "category",

    "entity_id": "12",

    "action": "update",

    "summary": "Updated category #12 (name, slug)",

    "undone_at": null,

    "undone_by_id": null,

    "created_at": "2026-10-06 10:41:05",

    "before": {"name": "Shoes", "slug": "shoes"},

    "after": {"name": "Sneakers", "slug": "sneakers"},

    "undone": false

  }

}
```

La réponse porte les champs de la liste, plus ceux-ci.

| Champ          | Signification                                                                                                              |
| -------------- | -------------------------------------------------------------------------------------------------------------------------- |
| `key_id`       | La clé qui a fait le changement, ou `null` pour une annulation faite depuis le dashboard.                                  |
| `undone_by_id` | L'id du changement qui a annulé celui-ci, sinon `null`.                                                                    |
| `before`       | Les valeurs que le changement a remplacées. `null` pour un `create`.                                                       |
| `after`        | Les valeurs que le changement a écrites. `null` pour un `delete` et pour une synchronisation des tarifs d'un transporteur. |

Le jeton d'accès (`access token`) d'un pixel n'est jamais gardé dans un changement. Quand le pixel en a un, `before` et `after` affichent `••••••••` à sa place.

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

| HTTP | Code          | Cause                                                                                                                                                        |
| ---- | ------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| 400  | `bad_request` | L'id du chemin n'est pas composé uniquement de chiffres.                                                                                                     |
| 403  | `forbidden`   | La clé n'a pas `store:read` ou la portée de lecture de l'entité du changement (`Missing scope: ...`), ou l'appel vient du jeton d'une application installée. |
| 404  | `not_found`   | Aucun changement avec cet id dans la boutique.                                                                                                               |

## `POST /v1/changes/{id}/undo`[​](#post-v1changesidundo "Lien direct vers post-v1changesidundo")

Réécrit les valeurs `before` du changement en passant par les mêmes contrôles que l'écriture d'origine, puis enregistre l'annulation comme un nouveau changement. Pas de corps de requête.

**Auth :** clé plateforme avec la portée d'annulation de l'entité du changement, d'après le tableau ci-dessus ; `store:read` n'est pas nécessaire. **Nécessite `Idempotency-Key`.** Le jeton d'une application installée reçoit `403 forbidden` (`Apps cannot use this endpoint`). Le propriétaire de la boutique voit les 20 derniers changements de chaque application installée sur la page de cette application dans le dashboard (`/dashboard/apps/{id}`, bloc **Ses dernières modifications**) et peut les annuler depuis là avec le bouton **Annuler**.

### Ce que fait l'annulation[​](#ce-que-fait-lannulation "Lien direct vers Ce que fait l'annulation")

1. Un changement qui a créé quelque chose n'a rien à restaurer et répond `422 nothing_to_restore` : supprimez l'élément à la place. L'ajout d'une section de la page d'accueil par `/v1/store/home-layout` fait exception, parce que chaque écriture de la mise en page de l'accueil est enregistrée comme un `update` de toute la mise en page.
2. Un `update` s'annule en réécrivant les valeurs `before` par-dessus ce qui est là maintenant. Seule la page d'accueil vérifie les changements ultérieurs et répond `409 layout_changed` (voir [Sections de la page d'accueil](https://dzbuild.com/fr/fr/api-docs/resources/home-layout.md)). Pour revenir sur plusieurs changements d'un même élément, annulez-les du plus récent au plus ancien.
3. Une catégorie supprimée revient avec son id, sans son image. Un code promo supprimé revient avec son id si aucun autre code ne l'a pris, et avec son nombre d'utilisations.
4. Un pixel supprimé revient avec son id s'il est encore libre, mais sans son jeton d'accès et sans ses affectations aux produits, catégories et landing pages. Annuler une modification de pixel laisse son jeton d'accès actuel en place.
5. Une section de landing page supprimée revient avec un nouvel id.
6. Une annulation de stock remet chaque valeur au nombre enregistré avant le changement, quoi que les commandes aient fait au stock depuis.
7. Annuler `POST /v1/shipping/rates` remet les tarifs précédents, et retire le tarif d'une wilaya qui n'en avait pas avant le changement. Annuler une synchronisation des tarifs d'un transporteur remet chaque tarif que la synchronisation a écrasé, et garde les tarifs qu'elle a ajoutés pour des wilayas qui n'en avaient pas.
8. L'annulation est enregistrée comme un changement à part, `undo_change_id`, que vous pouvez annuler pour réappliquer le changement d'origine. Quand le changement d'origine n'a pas d'`after` (une suppression ou une synchronisation des tarifs d'un transporteur), annuler l'annulation répond `422 nothing_to_restore`.
9. Un changement s'annule une seule fois. Une annulation suivante répond `409 already_undone`, et de deux annulations envoyées au même moment une seule s'exécute.
10. Quand la restauration elle-même échoue (`restore_target_missing`, une valeur refusée ou un `500`), le changement n'est pas marqué comme annulé : vous pouvez réessayer une fois la cause corrigée. Les règles de réessai sont plus bas.

La réponse de l'annulation ne contient pas les valeurs restaurées. Relisez l'élément. Par `api.dzbuild.app`, le cache de lecture court (`GET /v1/store` et chaque `GET` en dessous, les listes `GET /v1/products` et `GET /v1/landing-pages`) peut encore renvoyer les anciennes valeurs jusqu'à 30 secondes après l'annulation.

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

```
curl -X POST 'https://api.dzbuild.app/v1/changes/118/undo' \

  -H "Authorization: Bearer $DZ_KEY" \

  -H "Idempotency-Key: undo-118"
```

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

```
{

  "data": {

    "undone": true,

    "change_id": 118,

    "entity": "category",

    "undo_change_id": 121

  }

}
```

| Champ            | Signification                                                                             |
| ---------------- | ----------------------------------------------------------------------------------------- |
| `undone`         | Toujours `true` avec un `200`.                                                            |
| `change_id`      | Le changement annulé.                                                                     |
| `entity`         | Son entité.                                                                               |
| `undo_change_id` | Le changement qui enregistre cette annulation, ou `null` s'il n'a pas pu être enregistré. |

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

| HTTP | Code                            | Cause                                                                                                                                                     |
| ---- | ------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------- |
| 400  | `bad_request`                   | L'id du chemin n'est pas composé uniquement de chiffres, ou `Idempotency-Key` est absente ou mal formée.                                                  |
| 403  | `forbidden`                     | La clé n'a pas la portée d'annulation de l'entité du changement (`Missing scope: ...`), ou l'appel vient du jeton d'une application installée.            |
| 404  | `not_found`                     | Aucun changement avec cet id dans la boutique.                                                                                                            |
| 409  | `already_undone`                | Le changement a déjà été annulé.                                                                                                                          |
| 409  | `layout_changed`                | Page d'accueil seulement : la mise en page a changé après ce changement.                                                                                  |
| 422  | `not_undoable`                  | Ce type de changement ne peut pas être annulé.                                                                                                            |
| 422  | `nothing_to_restore`            | Le changement a créé quelque chose, ou ne contient aucune valeur à restaurer. Supprimez l'élément à la place.                                             |
| 422  | `restore_target_missing`        | Ce que le changement a touché n'existe plus, par exemple une catégorie ou une section de landing page supprimée depuis.                                   |
| 422  | `idempotency_key_reuse`         | La même `Idempotency-Key` a déjà servi pour une autre requête, par exemple l'annulation d'un autre changement.                                            |
| 4xx  | Le code de l'écriture d'origine | Les contrôles de l'écriture d'origine refusent les valeurs, par exemple `invalid_wilaya` sur les tarifs de livraison, ou un thème qui n'est plus proposé. |
| 500  | `server_error`                  | L'annulation n'a pas pu s'exécuter. Le changement reste annulable.                                                                                        |

### Réessais et `Idempotency-Key`[​](#réessais-et-idempotency-key "Lien direct vers réessais-et-idempotency-key")

La première réponse à une clé est conservée 24 heures, un `4xx` compris. Un réessai avec la même clé pour le même changement renvoie cette réponse avec `Idempotency-Replay: 1`, et aucune seconde annulation ne s'exécute. Après avoir corrigé la cause d'une erreur, réessayez donc avec une nouvelle clé. Une réponse `5xx` ou `429` n'est jamais conservée : réessayez-la avec la même clé. Voir [Idempotence](https://dzbuild.com/fr/fr/api-docs/idempotency.md).
