Store menus
The store menu is the merchant's choice of links for the navbar, the phone side menu and the footer. The API reads and writes it as one document: up to 12 top links with up to 10 sub-links each, up to 3 footer groups of 10 links, and one switch for the wide category panel. A store that never saved a menu shows the automatic links, and these two endpoints answer automatic: true for it.
The merchant edits the same document in the dashboard, in Customize, row Menu et liens / القائمة والروابط. See Menu and footer links in the merchant docs.
Before you start
- Reads need
store:readand writes needstore:write. Merchant keys carry both. PUTreplaces the whole document. Read it first, change what you need, and send it back with theversionyou read.Idempotency-Keyis optional onPUT. A keyedPUTis replayed for 24 hours. See Idempotency.
The menu document
| Field | Type | Notes |
|---|---|---|
v | int | Always 1. |
mega | boolean | true turns on the wide category panel. See The wide panel. |
top | array of items | The navbar links, in order. 1 to 12. home, products, categories, track, contact and cart appear at most once each here; children are not counted. |
footer | array of groups | The footer columns, in order. Up to 3. |
Items
| Field | Type | Notes |
|---|---|---|
type | string | One of home, products, categories, category, page, track, contact, cart, url. See Item types. |
label | string | Up to 40 characters. Empty means the target's own name, in the visitor's language for the built-in types. Required for url. |
ref | int or null | The category id for category, the page id for page, null for the other types. |
url | string | The address for url, up to 500 bytes. Empty for the other types. |
show_subcategories | boolean | Top-level category items only: also shows up to 10 subcategories of that category. Stored false everywhere else, children and footer links included. |
children | array of items | Top items only: up to 10 sub-links. A sub-link has no children of its own and cannot be a categories item. |
Groups
| Field | Type | Notes |
|---|---|---|
key | string or null | quick_links or categories for the two automatic groups, null for a group you add. |
label | string | The column title, up to 40 characters. It may be empty only when key is set: the store then shows the automatic title in the visitor's language. A typed title shows the same in every language. |
links | array of items | 1 to 10 links. Footer links have no children and no show_subcategories. |
Labels are stored as plain text: HTML tags are removed and spaces are trimmed. The whole document is at most 16 KB.
Item types
type | The link opens |
|---|---|
home | /, the store home. |
products | /products, all products. |
categories | A drop-down list of the main categories and their subcategories, with an all-products row. In the footer, the categories part of the home page. |
category | /category/{ref}. ref must be a category of this store. |
page | /pages/{slug} of the page ref. ref must be a published page of this store. See Store pages. |
track | /track-order. |
contact | The contact part of the home page. |
cart | /cart. |
url | url: a store path such as /pages/delivery, an anchor such as #products, an http:// or https:// address, tel: or mailto:. |
A url without a scheme that looks like a domain, such as instagram.com/x, is stored as https://instagram.com/x. javascript:, data:, //, backslashes, quotes, spaces and angle brackets are refused with bad_url.
On the store, a link to a category or a page that was deleted since, or a page turned back into a draft, is left out with its sub-links. An inactive category is not shown.
The wide panel
With "mega": true, a top item whose sub-links have their own sub-links shows on computers as a wide panel, one column per sub-link. The categories item does when a main category has subcategories. A category item with show_subcategories lists up to 10 subcategories, and with mega up to 10 of their own subcategories each, a third level. The categories item keeps the caps of the automatic menu: 10 main categories with 5 subcategories each in the navbar, 12 with 6 on the Promobi theme. On phones the side menu shows the same levels as an accordion.
GET /v1/store/menus
The store's menu. For a store that never saved one, menus is the automatic menu and automatic is true.
Auth: platform key with store:read.
Request
curl 'https://api.dzbuild.app/v1/store/menus' \
-H "Authorization: Bearer $DZ_KEY"
Response 200
version is the token to send back with PUT. max gives the limits and types the accepted item types.
{
"data": {
"automatic": false,
"menus": {
"v": 1,
"mega": false,
"top": [
{"type": "home", "label": "", "ref": null, "url": "", "show_subcategories": false, "children": []},
{"type": "category", "label": "", "ref": 152, "url": "", "show_subcategories": true, "children": []},
{"type": "page", "label": "", "ref": 7, "url": "", "show_subcategories": false, "children": []}
],
"footer": [
{"label": "Infos", "links": [{"type": "track", "label": "", "ref": null, "url": ""}, {"type": "url", "label": "Instagram", "ref": null, "url": "https://instagram.com/my-store"}]}
]
},
"version": "3f9a1c07d2e4b851",
"max": {"top": 12, "children": 10, "groups": 3, "links": 10, "label": 40},
"types": ["home", "products", "category", "page", "track", "contact", "cart", "url", "categories"]
},
"meta": {"request_id": "5d2a8e1f07c3b946", "api_version": "v1"}
}
PUT /v1/store/menus
Replaces the whole menu.
Auth: platform key with store:write. Idempotency-Key optional.
Body
| Field | Type | Required | Notes |
|---|---|---|---|
menus | object or null | yes | The new document. null brings back the automatic menu. |
version | string | no | The version you read. When it no longer matches, the call answers 409 write_conflict and changes nothing. Without it, the call overwrites whatever is stored. |
Request
curl -X PUT 'https://api.dzbuild.app/v1/store/menus' \
-H "Authorization: Bearer $DZ_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: menus-sizes-1" \
-d '{"version": "3f9a1c07d2e4b851", "menus": {"v": 1, "mega": false, "top": [{"type": "home", "label": "", "ref": null, "url": "", "show_subcategories": false, "children": []}, {"type": "page", "label": "Guide des tailles", "ref": 9, "url": "", "show_subcategories": false, "children": []}], "footer": []}}'
Response 200
menus is the document as stored, version the new token and change_id the change log id. A body that changes nothing answers change_id: null.
{
"data": {
"menus": {
"v": 1,
"mega": false,
"top": [
{"type": "home", "label": "", "ref": null, "url": "", "show_subcategories": false, "children": []},
{"type": "page", "label": "Guide des tailles", "ref": 9, "url": "", "show_subcategories": false, "children": []}
],
"footer": []
},
"version": "b70e4d2a9c15f383",
"change_id": 90420,
"automatic": false
},
"meta": {"request_id": "a81c3f5e92d07b64", "api_version": "v1"}
}
"menus": null answers automatic: true with the automatic menu in menus.
Undo
PUT answers with a change_id. POST /v1/changes/{change_id}/undo puts back the menu as it was before that call, the automatic menu included. The undo needs store:write and an Idempotency-Key.
- When the menu changed again since that call, the undo answers
409 menus_changedand changes nothing. Undo the newer change first. - A change undone a second time answers
409 already_undone. - Menus the merchant saves in Customize are not recorded, so they cannot be undone through the API.
GET /v1/changes?entity=store.menuslists the menu changes, newest first, withstore:read.entity_idismenus.- An installed app's token cannot read or undo changes:
GET /v1/changesand the undo answer403 forbidden. Write answers still carry thechange_id.
curl -X POST 'https://api.dzbuild.app/v1/changes/90420/undo' \
-H "Authorization: Bearer $DZ_KEY" \
-H "Idempotency-Key: undo-90420"
Errors
| HTTP | Code | Cause |
|---|---|---|
| 400 | bad_request | The body is not a JSON object, menus is missing or is neither an object nor null, or version is not a string. |
| 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. |
| 404 | not_found | The store does not exist. |
| 409 | write_conflict | The version you sent no longer matches: the error carries the current menus (null while the store shows the automatic links) and version, so you can merge and retry. The same code without them means another write landed at the same moment: read again, then retry. |
| 409 | menus_changed | Undo only: the menu changed since the change you undo. |
| 409 | already_undone | Undo only: the change was already undone. |
| 413 | payload_too_large | The body is over 1 MB. |
| 422 | invalid_menus | A value was refused. fields gives {path, code} for it. See the codes below. |
| 422 | idempotency_key_reuse | The same Idempotency-Key was used with another body. |
| 429 | rate_limited | Too many requests. Wait for Retry-After. |
| 500 | server_error | The request failed. Retry with the same Idempotency-Key. |
path starts with menus. and points at the refused value, such as menus.top.0.url or menus.footer.1.links.10; it is menus alone for too_large. The code is one of these.
code | Cause |
|---|---|
bad_url | An empty, refused or too long url. |
invalid | A shape error: a list where an object is expected or the reverse, such as top that is not an array. |
too_many | More than 12 top items, 10 children, 3 groups or 10 links in a group. path names the first item over the limit. |
top_required | top is empty. To drop a custom menu, send "menus": null. |
unknown_type | type is not one of the nine types. |
unknown_key | A footer group key other than quick_links, categories or null. |
unknown_category | ref is not a category of this store. |
unknown_page | ref is not a published page of this store. |
label_required | A url item without a label, or a footer group without a label and without a key. |
label_too_long | A label over 40 characters. |
links_required | A footer group with no link. |
duplicate_type | A second home, products, categories, track, contact or cart item in top. |
nested_too_deep | children inside a sub-link or a footer link, or a categories item as a sub-link. |
too_large | The document is over 16 KB. |
{
"error": {
"code": "invalid_menus",
"message": "Invalid menu at menus.top.0.url (bad_url); the caps and types are in GET /v1/store/menus.",
"fields": [{"path": "menus.top.0.url", "code": "bad_url"}]
},
"meta": {"request_id": "c27e90b4f13a5d68", "api_version": "v1"}
}
Limits
- Top menu: 12 items, each with up to 10 sub-links.
- Footer: 3 groups of up to 10 links.
- Label: 40 characters. Url: 500 bytes. Document: 16 KB.
- The limits are the same on every plan.
MCP tools
The MCP server, version 1.9.0, gives an AI assistant two menu tools with the same rules: menus_get returns the whole document with its version and caps, menus_set writes it back with that version, and change_undo reverts a write.