# Pages de la boutique

Les pages de la boutique sont les pages de texte du marchand, comme À propos, Livraison ou Retours. Chaque page est écrite en Markdown. Une page publiée vit à l'adresse `/pages/{slug}` de la boutique et figure dans son `sitemap.xml` et son `llms.txt`. Un brouillon répond `404` sur la boutique. Ces cinq endpoints listent, lisent, créent, modifient et suppriment les pages.

Le marchand modifie les mêmes pages dans le tableau de bord, dans Personnaliser, ligne **Pages**. Voir [Pages de la boutique](https://dzbuild.com/fr/docs/storefront/pages) dans la documentation marchand.

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

* Les lectures demandent `store:read` et les écritures `store:write`. Les clés du marchand portent les deux.
* `POST`, `PATCH` et `DELETE` demandent une `Idempotency-Key`. Voir [Idempotence](https://dzbuild.com/fr/fr/api-docs/idempotency.md).
* L'API ne peut pas envoyer d'images. Une image dans `body_md` est un lien vers une image déjà en ligne, écrit `![text](https://...)`.

## L'objet page[​](#lobjet-page "Lien direct vers L'objet page")

| Champ             | Type           | Notes                                                                                                                                                                                                                             |
| ----------------- | -------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `id`              | int            | L'id de la page.                                                                                                                                                                                                                  |
| `slug`            | string         | La fin de l'adresse de la page, après `/pages/`. 2 à 80 lettres latines minuscules, chiffres ou tirets, qui commencent et finissent par une lettre ou un chiffre. Unique dans la boutique.                                        |
| `title`           | string         | 1 à 120 caractères.                                                                                                                                                                                                               |
| `body_md`         | string         | Le texte en Markdown, jusqu'à 65 536 octets. Voir [Comment le texte est affiché](#how-the-body-is-rendered).                                                                                                                      |
| `seo_title`       | string ou null | Le titre que montrent les moteurs de recherche, 70 caractères au plus. `null` reprend `title`.                                                                                                                                    |
| `seo_description` | string ou null | La description que montrent les moteurs de recherche, 160 caractères au plus. `null` reprend les premières lignes du texte.                                                                                                       |
| `status`          | string         | `draft` ou `published`.                                                                                                                                                                                                           |
| `translations`    | object ou null | Les textes de la deuxième langue de la boutique, indexés par code de langue, comme `{"fr": {"title": "...", "body_md": "...", "seo_title": null, "seo_description": null}}`, ou `null`. Voir [Deuxième langue](#second-language). |
| `url`             | string         | L'adresse complète de la page sur la boutique.                                                                                                                                                                                    |
| `created_at`      | string         | `YYYY-MM-DD HH:MM:SS`, heure du serveur.                                                                                                                                                                                          |
| `updated_at`      | string         | `YYYY-MM-DD HH:MM:SS`, heure du serveur.                                                                                                                                                                                          |

Les balises HTML de `title`, `seo_title` et `seo_description` sont retirées, et un texte trop long est coupé à la limite.

### Comment le texte est affiché[​](#how-the-body-is-rendered "Lien direct vers Comment le texte est affiché")

* Le Markdown est affiché en mode sûr : le HTML brut est échappé et s'affiche comme du texte.
* Un simple retour à la ligne dans le texte reste un retour à la ligne.
* Un lien vers la boutique elle-même, comme `/products`, `/category/12` ou `/pages/delivery`, s'ouvre dans le même onglet. Un lien vers un autre site s'ouvre dans un nouvel onglet, comme un lien qui commence par `//`.
* Un lien YouTube seul sur sa ligne affiche la vidéo.

### Deuxième langue[​](#second-language "Lien direct vers Deuxième langue")

`translations` n'est écrit que si l'extension Boutique multilingue est active sur la boutique. Sans elle, le champ est ignoré et la valeur enregistrée est gardée. La clé est la deuxième langue de la boutique : `fr` pour une boutique en arabe, `ar` pour une boutique en français. Un client qui parcourt la boutique dans cette langue voit ces textes, et un champ vide reprend le champ principal.

## `GET /v1/store/pages`[​](#get-v1storepages "Lien direct vers get-v1storepages")

Toutes les pages de la boutique, brouillons compris, triées par titre. Les éléments sont des résumés sans le texte : lisez une page pour obtenir son `body_md`. La liste n'est pas paginée : une boutique a au plus 25 pages.

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

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

```
curl 'https://api.dzbuild.app/v1/store/pages' \

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

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

Chaque élément porte `id`, `slug`, `title`, `status`, `url` et `updated_at`. `cap` est le nombre de pages que le plan de la boutique autorise. `max_pages` est la limite fixe pour tous les plans.

```
{

  "data": {

    "items": [

      {

        "id": 7,

        "slug": "retours",

        "title": "Retours",

        "status": "published",

        "url": "https://my-store.dzbuild.app/pages/retours",

        "updated_at": "2026-11-06 10:12:40"

      }

    ],

    "cap": 25,

    "max_pages": 25

  },

  "meta": {"request_id": "3f1c9a7b20d4e865", "api_version": "v1"}

}
```

## `GET /v1/store/pages/{id}`[​](#get-v1storepagesid "Lien direct vers get-v1storepagesid")

Une page.

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

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

```
curl 'https://api.dzbuild.app/v1/store/pages/7' \

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

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

La réponse est l'objet page. Une page qui n'est pas dans la boutique répond `404 page_not_found`.

## `POST /v1/store/pages`[​](#post-v1storepages "Lien direct vers post-v1storepages")

Crée une page.

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

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

| Champ             | Type                   | Obligatoire | Notes                                                                                                                                                                                                                                                                                                                    |
| ----------------- | ---------------------- | ----------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `title`           | string                 | oui         | 1 à 120 caractères.                                                                                                                                                                                                                                                                                                      |
| `slug`            | string                 | non         | Fait à partir du titre si vous ne l'envoyez pas. Quand cela ne donne pas de `slug` utilisable (un titre sans lettre latine ni chiffre comme un titre en arabe, un mot réservé, ou un `slug` qu'une autre page utilise), la page reçoit `page-N`. Un `slug` envoyé est mis en minuscules, puis doit être valide et libre. |
| `body_md`         | string                 | non         | Markdown, jusqu'à 65 536 octets.                                                                                                                                                                                                                                                                                         |
| `seo_title`       | string ou null         | non         | 70 caractères au plus.                                                                                                                                                                                                                                                                                                   |
| `seo_description` | string ou null         | non         | 160 caractères au plus.                                                                                                                                                                                                                                                                                                  |
| `status`          | `draft` ou `published` | non         | `draft` par défaut. Publiez seulement si le marchand l'a demandé.                                                                                                                                                                                                                                                        |
| `translations`    | object ou null         | non         | Voir [Deuxième langue](#second-language).                                                                                                                                                                                                                                                                                |

Un `slug` ne peut pas être un mot que la boutique utilise déjà pour ses propres adresses, comme `products`, `product`, `cart`, `checkout`, `category`, `track-order`, `landing`, `pages`, `sitemap`, `ar` ou `fr`. Un tel `slug` répond `422 invalid_page`.

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

```
curl -X POST 'https://api.dzbuild.app/v1/store/pages' \

  -H "Authorization: Bearer $DZ_KEY" \

  -H "Content-Type: application/json" \

  -H "Idempotency-Key: page-create-retours-1" \

  -d '{"title": "Retours", "slug": "retours", "body_md": "## Comment retourner un article\n\nÉcrivez-nous sur WhatsApp avec votre numéro de commande.", "status": "published"}'
```

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

```
{

  "data": {

    "page": {

      "id": 7,

      "slug": "retours",

      "title": "Retours",

      "body_md": "## Comment retourner un article\n\nÉcrivez-nous sur WhatsApp avec votre numéro de commande.",

      "seo_title": null,

      "seo_description": null,

      "status": "published",

      "translations": null,

      "url": "https://my-store.dzbuild.app/pages/retours",

      "created_at": "2026-11-06 10:12:40",

      "updated_at": "2026-11-06 10:12:40"

    },

    "change_id": 90310

  },

  "meta": {"request_id": "8b2e04d1c9a7f356", "api_version": "v1"}

}
```

## `PATCH /v1/store/pages/{id}`[​](#patch-v1storepagesid "Lien direct vers patch-v1storepagesid")

Modifie seulement les champs envoyés. Le corps prend les champs de `POST`, tous facultatifs. Un corps sans aucun d'eux répond `422 no_changes`, et un appel qui ne change rien répond `change_id: null`.

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

* Un nouveau `slug` change l'adresse de la page. L'ancienne adresse continue de répondre par une redirection `301` vers la nouvelle, donc les liens partagés marchent toujours. Remettre l'ancien `slug` est permis. Sans `slug`, l'adresse reste la même, même quand le titre change.
* `"status": "draft"` retire la page de la boutique sans la supprimer.

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

```
curl -X PATCH 'https://api.dzbuild.app/v1/store/pages/7' \

  -H "Authorization: Bearer $DZ_KEY" \

  -H "Content-Type: application/json" \

  -H "Idempotency-Key: page-7-seo-1" \

  -d '{"seo_title": "Retours et échanges", "seo_description": "Comment retourner un article commandé sur notre boutique."}'
```

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

La réponse a les mêmes champs que la réponse de `POST` : `page`, la page après le changement, et `change_id`.

## `DELETE /v1/store/pages/{id}`[​](#delete-v1storepagesid "Lien direct vers delete-v1storepagesid")

Supprime une page. Son adresse sur la boutique ne marche plus tout de suite.

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

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

```
curl -X DELETE 'https://api.dzbuild.app/v1/store/pages/7' \

  -H "Authorization: Bearer $DZ_KEY" \

  -H "Idempotency-Key: page-7-delete-1"
```

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

```
{

  "data": {

    "deleted": true,

    "id": 7,

    "change_id": 90312

  },

  "meta": {"request_id": "c40a91e7b25d3f68", "api_version": "v1"}

}
```

## Annuler[​](#annuler "Lien direct vers Annuler")

Chaque écriture répond avec un `change_id`. `POST /v1/changes/{change_id}/undo` annule ce changement. L'annulation demande `store:write` et une `Idempotency-Key`.

* Annuler un `PATCH` remet la page comme elle était avant cet appel. Si la page a été supprimée depuis, l'annulation répond `422 restore_target_missing`.
* Annuler un `DELETE` recrée la page sous son ancien id et son ancien slug.
* Une création ne peut pas être annulée : l'annulation répond `422 nothing_to_restore`. Supprimez plutôt la page.
* Si une autre page a pris depuis l'id ou le slug de la page supprimée, ou si la boutique a déjà 25 pages, l'annulation de la suppression répond `409 page_restore_conflict`. Renommez ou supprimez cette page, puis annulez à nouveau.
* Un changement annulé une seconde fois répond `409 already_undone`.
* Les changements que le marchand enregistre dans Personnaliser ne sont pas enregistrés dans l'historique, donc ils ne peuvent pas être annulés par l'API.
* `GET /v1/changes?entity=store.page` liste les changements des pages, les plus récents d'abord, avec `store:read`. `entity_id` est l'id de la page.
* Le jeton d'une application installée ne peut ni lire ni annuler les changements : `GET /v1/changes` et l'annulation répondent `403 forbidden` (« Apps cannot use this endpoint »). Les réponses d'écriture portent quand même le `change_id`.

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

  -H "Authorization: Bearer $DZ_KEY" \

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

```
{

  "data": {

    "undone": true,

    "change_id": 90312,

    "entity": "store.page",

    "undo_change_id": 90315

  },

  "meta": {"request_id": "71d5e0a2c8b94f36", "api_version": "v1"}

}
```

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

| HTTP | Code                     | Cause                                                                                                                                                                                                                                                                                                                       |
| ---- | ------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| 400  | `bad_request`            | Le corps n'est pas un objet JSON, l'id de la page dans le chemin n'est pas un nombre positif, 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: store<!-- -->:read<!-- --> » ou « Missing scope: store<!-- -->:write<!-- --> », ou « API access requires an active Enterprise plan » pour une clé du marchand dont la boutique n'a pas de plan Enterprise actif.                                                                                           |
| 403  | `plan_required`          | Une création dépasserait le nombre de pages que le plan autorise. L'erreur porte `plan` et `cap`.                                                                                                                                                                                                                           |
| 404  | `page_not_found`         | Aucune page avec cet id dans la boutique.                                                                                                                                                                                                                                                                                   |
| 409  | `slug_taken`             | Une autre page de la boutique utilise ce `slug`. `fields` vaut `[{"path": "slug", "code": "taken"}]`.                                                                                                                                                                                                                       |
| 409  | `write_conflict`         | Une autre écriture est arrivée au même moment. Lisez la page, puis réessayez avec une nouvelle `Idempotency-Key`.                                                                                                                                                                                                           |
| 409  | `already_undone`         | Annulation seulement : le changement a déjà été annulé.                                                                                                                                                                                                                                                                     |
| 409  | `page_restore_conflict`  | Annulation d'une suppression seulement : une autre page utilise maintenant l'id ou le slug de la page, ou la boutique a déjà 25 pages.                                                                                                                                                                                      |
| 413  | `payload_too_large`      | Le corps dépasse 1 Mo.                                                                                                                                                                                                                                                                                                      |
| 422  | `invalid_page`           | Une valeur a été refusée : un titre absent ou vide, un champ qui n'est pas du texte, un `slug` mal formé ou réservé, un `status` autre que `draft` et `published`, des `translations` qui ne sont pas un objet, ou un `body_md` de plus de 65 536 octets. `fields` donne `{path, code}` pour le champ refusé, comme `slug`. |
| 422  | `limit_reached`          | La boutique a déjà 25 pages. L'erreur porte `limit`.                                                                                                                                                                                                                                                                        |
| 422  | `no_changes`             | Un `PATCH` sans aucun des champs de la page.                                                                                                                                                                                                                                                                                |
| 422  | `nothing_to_restore`     | Annulation seulement : le changement a créé la page.                                                                                                                                                                                                                                                                        |
| 422  | `restore_target_missing` | Annulation d'un `PATCH` seulement : la page a été supprimée depuis.                                                                                                                                                                                                                                                         |
| 422  | `idempotency_key_reuse`  | La même `Idempotency-Key` a servi avec un autre corps.                                                                                                                                                                                                                                                                      |
| 429  | `rate_limited`           | Trop de requêtes, dont plus de 30 écritures de pages par minute pour la boutique. Attendez `Retry-After`.                                                                                                                                                                                                                   |
| 429  | `too_many_concurrent`    | Plus de 5 écritures de pages en cours en même temps pour la boutique. Réessayez dans quelques secondes.                                                                                                                                                                                                                     |
| 500  | `server_error`           | La requête a échoué. Réessayez avec la même `Idempotency-Key`.                                                                                                                                                                                                                                                              |

Une réponse `invalid_page` ressemble à ceci.

```
{

  "error": {

    "code": "invalid_page",

    "message": "slug: 2 to 80 lowercase latin letters, digits or dashes, not a store route",

    "fields": [{"path": "slug", "code": "invalid"}]

  },

  "meta": {"request_id": "e9a3c17f04b26d58", "api_version": "v1"}

}
```

## Limites[​](#limites "Lien direct vers Limites")

* **25 pages** par boutique (`max_pages`), brouillons compris.
* **Plafond du plan** (`cap`) : 3 pages sur Free ou un plan expiré, 25 à partir de Pro. Une boutique au-dessus de son plafond après un changement de plan garde ses pages et peut toujours les lire, les modifier et les supprimer. Une création répond `403 plan_required` tant que la boutique n'est pas sous son plafond.
* **Texte** : `body_md` jusqu'à 65 536 octets, et la requête jusqu'à 1 Mo.
* **Écritures** : 30 par minute et 5 en même temps par boutique, en plus de la limite par minute de la boutique. Voir [Limites de taux](https://dzbuild.com/fr/fr/api-docs/rate-limits.md).

## Outils MCP[​](#outils-mcp "Lien direct vers Outils MCP")

Le serveur MCP, en version 1.8.0, donne à un assistant IA cinq outils de pages avec les mêmes règles : `pages_list`, `page_get`, `page_create`, `page_update` et `page_delete`. L'outil `page_delete` demande à l'assistant de confirmer d'abord avec le marchand.
