Store pages
Store pages are the merchant's text pages, such as About us, Delivery or Returns. Each page is written in Markdown. A published page lives at /pages/{slug} on the store and is listed in the store's sitemap.xml and llms.txt. A draft answers 404 on the store. These five endpoints list, read, create, change and delete the pages.
The merchant edits the same pages in the dashboard, in Customize, row Pages. See Store pages in the merchant docs.
Before you start
- Reads need
store:readand writes needstore:write. Merchant keys carry both. POST,PATCHandDELETEneed anIdempotency-Key. See Idempotency.- The API cannot upload images. An image in
body_mdis a link to a picture that is already online, written as.
The page object
| Field | Type | Notes |
|---|---|---|
id | int | The page id. |
slug | string | The end of the page address, after /pages/. 2 to 80 lowercase Latin letters, digits or dashes, starting and ending with a letter or a digit. Unique in the store. |
title | string | 1 to 120 characters. |
body_md | string | The text in Markdown, up to 65,536 bytes. See How the body is rendered. |
seo_title | string or null | The title search engines show, up to 70 characters. null uses title. |
seo_description | string or null | The description search engines show, up to 160 characters. null uses the first lines of the text. |
status | string | draft or published. |
translations | object or null | The texts of the store's second language, keyed by language code, such as {"fr": {"title": "...", "body_md": "...", "seo_title": null, "seo_description": null}}, or null. See Second language. |
url | string | The full address of the page on the store. |
created_at | string | YYYY-MM-DD HH:MM:SS, server time. |
updated_at | string | YYYY-MM-DD HH:MM:SS, server time. |
HTML tags in title, seo_title and seo_description are removed, and text over the limit is cut.
How the body is rendered
- Markdown is rendered in safe mode: raw HTML is escaped and shows as text.
- A single line break in the text is kept as a line break.
- A link to the store itself, such as
/products,/category/12or/pages/delivery, opens in the same tab. A link to another site opens in a new tab, and so does a link that starts with//. - A YouTube link alone on its line shows the video.
Second language
translations is written only while the Multi-Language add-on is active on the store. Without it, the field is ignored and the stored value is kept. The key is the store's second language: fr for an Arabic store, ar for a French one. A buyer who browses the store in that language sees those texts, and an empty field falls back to the main field.
GET /v1/store/pages
All the store's pages, drafts included, ordered by title. The items are summaries without the text: read one page for its body_md. The list is not paginated: a store has at most 25 pages.
Auth: platform key with store:read.
Request
curl 'https://api.dzbuild.app/v1/store/pages' \
-H "Authorization: Bearer $DZ_KEY"
Response 200
Each item carries id, slug, title, status, url and updated_at. cap is the number of pages the store's plan allows. max_pages is the hard limit for every plan.
{
"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}
One page.
Auth: platform key with store:read.
Request
curl 'https://api.dzbuild.app/v1/store/pages/7' \
-H "Authorization: Bearer $DZ_KEY"
Response 200
The answer is the page object. A page that is not in the store answers 404 page_not_found.
POST /v1/store/pages
Creates a page.
Auth: platform key with store:write. Requires Idempotency-Key.
Body
| Field | Type | Required | Notes |
|---|---|---|---|
title | string | yes | 1 to 120 characters. |
slug | string | no | Made from the title when you leave it out. When that gives no usable slug (a title with no Latin letters or digits such as an Arabic one, a reserved word, or a slug another page uses), the page gets page-N. A slug you send is lowercased, then must be valid and free. |
body_md | string | no | Markdown, up to 65,536 bytes. |
seo_title | string or null | no | Up to 70 characters. |
seo_description | string or null | no | Up to 160 characters. |
status | draft or published | no | Defaults to draft. Publish only when the merchant asked. |
translations | object or null | no | See Second language. |
A slug must not be a word the store already uses for its own addresses, such as products, product, cart, checkout, category, track-order, landing, pages, sitemap, ar or fr. Such a slug answers 422 invalid_page.
Request
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"}'
Response 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}
Changes only the fields you send. The body takes the fields of POST, all optional. A body with none of them answers 422 no_changes, and a call that changes nothing answers change_id: null.
Auth: platform key with store:write. Requires Idempotency-Key.
- A new
slugchanges the page address. The old address keeps answering with a301redirect to the new one, so shared links keep working. Setting the old slug back is allowed. Leavingslugout keeps the address, even when the title changes. "status": "draft"takes the page off the store without deleting it.
Request
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."}'
Response 200
The answer has the same fields as the POST answer: page, the page after the change, and change_id.
DELETE /v1/store/pages/{id}
Deletes a page. Its address on the store stops working at once.
Auth: platform key with store:write. Requires Idempotency-Key.
Request
curl -X DELETE 'https://api.dzbuild.app/v1/store/pages/7' \
-H "Authorization: Bearer $DZ_KEY" \
-H "Idempotency-Key: page-7-delete-1"
Response 200
{
"data": {
"deleted": true,
"id": 7,
"change_id": 90312
},
"meta": {"request_id": "c40a91e7b25d3f68", "api_version": "v1"}
}
Undo
Every write answers with a change_id. POST /v1/changes/{change_id}/undo reverts that change. The undo needs store:write and an Idempotency-Key.
- Undoing a
PATCHputs back the page as it was before that call. When the page was deleted since, the undo answers422 restore_target_missing. - Undoing a
DELETEcreates the page again under its old id and slug. - A create cannot be undone: the undo answers
422 nothing_to_restore. Delete the page instead. - When another page took the id or the slug of a deleted page since, or the store already holds 25 pages, the undo of the delete answers
409 page_restore_conflict. Rename or delete that page, then undo again. - A change undone a second time answers
409 already_undone. - Changes the merchant saves in Customize are not recorded, so they cannot be undone through the API.
GET /v1/changes?entity=store.pagelists the page changes, newest first, withstore:read.entity_idis the page id.- An installed app's token cannot read or undo changes:
GET /v1/changesand the undo answer403 forbidden(Apps cannot use this endpoint). Write answers still carry thechange_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"}
}
Errors
| HTTP | Code | Cause |
|---|---|---|
| 400 | bad_request | The body is not a JSON object, the page id in the path is not a positive number, 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: store:read or Missing scope: store:write, or API access requires an active Enterprise plan for a merchant key whose store is not on an active Enterprise plan. |
| 403 | plan_required | A create would go over the number of pages the plan allows. The error carries plan and cap. |
| 404 | page_not_found | No page with this id in the store. |
| 409 | slug_taken | Another page of the store uses this slug. fields is [{"path": "slug", "code": "taken"}]. |
| 409 | write_conflict | Another write landed at the same moment. Read the page, then retry with a new Idempotency-Key. |
| 409 | already_undone | Undo only: the change was already undone. |
| 409 | page_restore_conflict | Undo of a delete only: another page now uses the page's id or slug, or the store already holds 25 pages. |
| 413 | payload_too_large | The body is over 1 MB. |
| 422 | invalid_page | A value was refused: a missing or empty title, a field that is not text, a malformed or reserved slug, a status other than draft and published, translations that is not an object, or a body_md over 65,536 bytes. fields gives {path, code} for the refused field, such as slug. |
| 422 | limit_reached | The store already has 25 pages. The error carries limit. |
| 422 | no_changes | A PATCH with none of the page fields. |
| 422 | nothing_to_restore | Undo only: the change created the page. |
| 422 | restore_target_missing | Undo of a PATCH only: the page was deleted since. |
| 422 | idempotency_key_reuse | The same Idempotency-Key was used with another body. |
| 429 | rate_limited | Too many requests, including more than 30 page writes a minute for the store. Wait for Retry-After. |
| 429 | too_many_concurrent | More than 5 page writes running at once for the store. Retry in a few seconds. |
| 500 | server_error | The request failed. Retry with the same Idempotency-Key. |
An invalid_page answer looks like this.
{
"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"}
}
Limits
- 25 pages per store (
max_pages), drafts included. - Plan cap (
cap): 3 pages on Free or an expired plan, 25 from Pro. A store above its cap after a downgrade keeps its pages and can still read, change and delete them. A create answers403 plan_requireduntil the store is under its cap. - Body:
body_mdup to 65,536 bytes, and the request up to 1 MB. - Writes: 30 a minute and 5 at once per store, on top of the store's per-minute limit. See Rate limits.
MCP tools
The MCP server, version 1.8.0, gives an AI assistant five page tools with the same rules: pages_list, page_get, page_create, page_update and page_delete. The page_delete tool tells the assistant to confirm with the merchant first.