Home page sections
The home layout is the ordered list of sections a store shows on its home page. Each section has a type and settings. Ten types can be added on every theme: category-products, featured, categories, banner, image-with-text, rich-text, trust-badges, testimonials, faq and video. A section theme such as atlas also offers hero and product-grid. types in the GET answer gives the settings of each. Every write on this page is live on the store as soon as it answers.
This is not GET /v1/store/home-sections, which reads the switches of the Digital, Ariana and Prestige home pages.
Before you start
GETneedsstore:readand the writes needstore:write. Merchant keys carry both.POST,PATCHandDELETEneed anIdempotency-Key. OnPUTit is optional. See Idempotency.- A category id comes from
GET /v1/categories, which needsproducts:read.
The section object
| Field | Type | Notes |
|---|---|---|
id | int | Stable while the section exists. A section that comes back through an undo gets a new id. |
type | string | One of the type values listed in types by GET /v1/store/home-layout. |
settings | object | Every setting of the type, with the defaults filled in. |
is_active | bool | false hides the section from buyers and keeps it in the list. |
available | bool | false when the store's theme no longer has this type. The section is kept as stored and a write may keep it. |
Settings of category-products
| Setting | Type | Default | Rules |
|---|---|---|---|
category | category id | 0 | A category of this store. 0 means none, and the section then shows nothing to buyers. An id of another store answers 422 invalid_settings. |
title | text | "" | Up to 80 characters, HTML removed. Empty shows the category name. |
count | range | 8 | 4 to 12. A number outside that range is moved to the nearest end. |
layout | select | grid | grid or slider. |
show_view_all | checkbox | true | A link to the category page. |
A category with no products shows nothing to buyers either. Read the rules of any type from its settings_schema rather than hard-coding them: the list of types depends on the store's theme.
Setting formats
| Setting type | Accepted value |
|---|---|
category | An id from GET /v1/categories, or 0 for none. |
link | #anchor, a /path on the store, an http:// or https:// address, or a tel: or mailto: link, up to 500 characters, or "". An address sent without its scheme, such as wa.me/213..., is stored with https:// in front. |
youtube | A YouTube video link or its 11-character id. The id is stored. |
image | The path of an image uploaded in the dashboard for this store, /uploads/banners/{store_id}/..., or "". The API cannot upload images today: the merchant uploads the picture in the section's settings in the dashboard first, and GET then returns its path. |
When buyers see a section
A section whose content is not filled in yet is stored and the write answers 2xx, but buyers do not see it until it is:
| Type | Buyers see it once |
|---|---|
category-products | category is a category of the store that has products. |
featured | the chosen source has products. |
categories | the store has a category with products, or any category when show_empty is true. |
banner | image is set. |
image-with-text | image is set, with a title or a text. |
rich-text | title or text is set. |
trust-badges | always. Empty badges 1 and 2 show the default delivery and cash on delivery lines. |
testimonials | at least one tN_text is set. |
faq | at least one qN is set with its aN. |
video | video holds a YouTube id. |
rendered does not change for this: it says whether the theme shows stored sections, not whether one section is visible. On a section theme (atlas), saved sections replace the theme's own home only while they include a visible product-grid section, and rendered is false until then; buyers see the theme's home, and GET lists only the saved sections, not the theme's own.
GET /v1/store/home-layout
The sections in render order, the section types that can be added on the store's theme, and the plan cap.
Auth: platform key with store:read.
Request
curl 'https://api.dzbuild.app/v1/store/home-layout' \
-H "Authorization: Bearer $DZ_KEY"
Response 200
{
"data": {
"theme": "starter",
"rendered": true,
"max_sections": 25,
"cap": 25,
"version": "9c1e04b7a2d35f68",
"sections": [
{
"id": 412,
"type": "category-products",
"settings": {
"category": 57,
"title": "",
"count": 8,
"layout": "grid",
"show_view_all": true
},
"is_active": true,
"available": true
},
{
"id": 415,
"type": "category-products",
"settings": {
"category": 61,
"title": "Nos parfums",
"count": 10,
"layout": "slider",
"show_view_all": false
},
"is_active": false,
"available": true
}
],
"types": [
{
"type": "category-products",
"name": {"ar": "منتجات فئة", "fr": "Produits d'une catégorie"},
"description": {"ar": "اعرض منتجات فئة واحدة في شبكة أو شريط تمرير.", "fr": "Affichez les produits d'une catégorie en grille ou en carrousel."},
"icon": "bi-grid-3x3-gap",
"limit": 12,
"settings_schema": [
{"id": "category", "type": "category", "default": 0, "label": {"ar": "الفئة", "fr": "Catégorie"}},
{"id": "title", "type": "text", "max": 80, "default": "", "label": {"ar": "العنوان (إذا تركته فارغاً يظهر اسم الفئة)", "fr": "Titre (si vide, le nom de la catégorie s'affiche)"}},
{"id": "count", "type": "range", "min": 4, "max": 12, "default": 8, "label": {"ar": "عدد المنتجات", "fr": "Nombre de produits"}},
{"id": "layout", "type": "select", "options": ["grid", "slider"], "default": "grid", "label": {"ar": "طريقة العرض", "fr": "Affichage"}, "option_labels": {"ar": ["شبكة", "شريط تمرير"], "fr": ["Grille", "Carrousel"]}},
{"id": "show_view_all", "type": "checkbox", "default": true, "label": {"ar": "زر عرض الكل", "fr": "Lien « Voir tout »"}}
]
}
]
},
"meta": {"request_id": "8f2c1a9d4b7e6035", "api_version": "v1"}
}
| Field | Meaning |
|---|---|
theme | The store's theme key. |
rendered | false when the store's current theme does not show home page sections. The sections are kept and show again on a theme that does. On a section theme it is true only while a visible product-grid section is saved. |
max_sections | 25, the most sections one home page holds. |
cap | The sections the store's plan allows: 3 on Free or an expired plan, 25 from Pro. |
version | A fingerprint of the stored layout. Send it back as version on PUT to refuse a layout that changed since this read. |
sections | The sections in render order, hidden ones included. |
types | The types that can be added on this theme, with name, description, icon, limit (the most sections of that type one page holds) and settings_schema. The sample above shows one of them. |
Reading after a write
Through api.dzbuild.app a successful GET is cached for 30 seconds per key and query string (the X-Cache: HIT|MISS header tells you which). A GET sent right after a write can therefore return the layout from before it. Use the layout in the write's answer: every write returns the whole list in render order with the new version. When you must read again, add a query string of your own, such as ?fresh=1727520000.
POST /v1/store/home-layout/sections
Adds one section.
Auth: platform key with store:write. Requires Idempotency-Key.
Body
| Field | Type | Required | Notes |
|---|---|---|---|
type | string | yes | A type from types. |
settings | object | no | Settings you do not send take the type's defaults. |
position | int | no | 0 puts the section at the top, 24 is the last place. Without it the section goes at the end. |
Request
curl -X POST 'https://api.dzbuild.app/v1/store/home-layout/sections' \
-H "Authorization: Bearer $DZ_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: hs-add-sacs-1" \
-d '{"type": "category-products", "settings": {"category": 64, "layout": "slider"}, "position": 0}'
Response 201
{
"data": {
"section": {
"id": 418,
"type": "category-products",
"settings": {"category": 64, "title": "", "count": 8, "layout": "slider", "show_view_all": true},
"is_active": true,
"available": true
},
"sections": [
{"id": 418, "type": "category-products", "settings": {"category": 64, "title": "", "count": 8, "layout": "slider", "show_view_all": true}, "is_active": true, "available": true},
{"id": 412, "type": "category-products", "settings": {"category": 57, "title": "", "count": 8, "layout": "grid", "show_view_all": true}, "is_active": true, "available": true},
{"id": 415, "type": "category-products", "settings": {"category": 61, "title": "Nos parfums", "count": 10, "layout": "slider", "show_view_all": false}, "is_active": false, "available": true}
],
"version": "e27a90c4b1f36d05",
"change_id": 90231,
"rendered": true
},
"meta": {"request_id": "3b7d0e5a9c14f862", "api_version": "v1"}
}
PATCH /v1/store/home-layout/sections/{id}
Changes one section. The settings you send are merged over the stored ones. Send settings, is_active or both.
Auth: platform key with store:write. Requires Idempotency-Key.
Body
| Field | Type | Required | Notes |
|---|---|---|---|
settings | object | no | Merged over the stored settings. |
is_active | bool | no | false hides the section, true shows it. |
replace | bool | no | With settings, true resets every setting you do not send to its default. |
Request
curl -X PATCH 'https://api.dzbuild.app/v1/store/home-layout/sections/415' \
-H "Authorization: Bearer $DZ_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: hs-415-show-1" \
-d '{"settings": {"count": 6}, "is_active": true}'
Response 200
The answer has the same fields as the POST answer: section (the section after the change), sections, version, change_id and rendered.
{
"data": {
"section": {
"id": 415,
"type": "category-products",
"settings": {"category": 61, "title": "Nos parfums", "count": 6, "layout": "slider", "show_view_all": false},
"is_active": true,
"available": true
},
"sections": [
{"id": 418, "type": "category-products", "settings": {"category": 64, "title": "", "count": 8, "layout": "slider", "show_view_all": true}, "is_active": true, "available": true},
{"id": 412, "type": "category-products", "settings": {"category": 57, "title": "", "count": 8, "layout": "grid", "show_view_all": true}, "is_active": true, "available": true},
{"id": 415, "type": "category-products", "settings": {"category": 61, "title": "Nos parfums", "count": 6, "layout": "slider", "show_view_all": false}, "is_active": true, "available": true}
],
"version": "51d8c3e06fa2b974",
"change_id": 90232,
"rendered": true
},
"meta": {"request_id": "c90a6e1f2d7b4538", "api_version": "v1"}
}
DELETE /v1/store/home-layout/sections/{id}
Removes one section.
Auth: platform key with store:write. Requires Idempotency-Key.
Request
curl -X DELETE 'https://api.dzbuild.app/v1/store/home-layout/sections/412' \
-H "Authorization: Bearer $DZ_KEY" \
-H "Idempotency-Key: hs-del-412-1"
Response 200
{
"data": {
"deleted": true,
"id": 412,
"sections": [
{"id": 418, "type": "category-products", "settings": {"category": 64, "title": "", "count": 8, "layout": "slider", "show_view_all": true}, "is_active": true, "available": true},
{"id": 415, "type": "category-products", "settings": {"category": 61, "title": "Nos parfums", "count": 6, "layout": "slider", "show_view_all": false}, "is_active": true, "available": true}
],
"version": "0f6b2d9e84a1c357",
"change_id": 90233,
"rendered": true
},
"meta": {"request_id": "71e4b08c3a5d9f26", "api_version": "v1"}
}
POST /v1/store/home-layout/reorder
Sets the render order. ids lists every section of the page exactly once, hidden ones included.
Auth: platform key with store:write. Requires Idempotency-Key.
Request
curl -X POST 'https://api.dzbuild.app/v1/store/home-layout/reorder' \
-H "Authorization: Bearer $DZ_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: hs-order-2" \
-d '{"ids": [415, 418]}'
Response 200
The answer carries sections in the new order, version, change_id and rendered. An id that is not on the page answers 404 section_not_found. A missing or repeated id answers 422 invalid_order.
PUT /v1/store/home-layout
Replaces the whole layout with the list you send, in that order.
Auth: platform key with store:write. Idempotency-Key is optional.
Body
| Field | Type | Required | Notes |
|---|---|---|---|
sections | array | yes | At most 25 items, each {id?, type, settings?, is_active?}. An empty list removes every section. |
version | string | no | The version from your last read. When the layout changed since, the call answers 409 write_conflict with the current sections and version, and writes nothing. |
How each item is read:
- An item with an
idkeeps that section. Itstypemust be the section's current type. - An item without an
idcreates a section. - A section of the page that is not in the list is deleted.
settingsis the whole object: a setting you leave out goes back to its default. Send the full settings of every section you keep.is_activedefaults totrue.
Request
curl -X PUT 'https://api.dzbuild.app/v1/store/home-layout' \
-H "Authorization: Bearer $DZ_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: hs-replace-7" \
-d '{
"version": "0f6b2d9e84a1c357",
"sections": [
{"id": 415, "type": "category-products", "settings": {"category": 61, "title": "Nos parfums", "count": 6, "layout": "slider", "show_view_all": false}},
{"type": "category-products", "settings": {"category": 57}}
]
}'
Response 200
The answer carries sections, version, change_id and rendered. Section 418 was left out, so it is deleted. Sending back the layout you read, unchanged, answers change_id: null.
With an Idempotency-Key, a retry with the same key and the same body gets the stored answer back for 24 hours with Idempotency-Replay: 1, and the same key with another body answers 422 idempotency_key_reuse. Without a key the call runs every time, which is safe: sending the same list twice leaves the same layout.
Undo
Every write answers with a change_id, or null when it changed nothing. POST /v1/changes/{change_id}/undo puts the whole home page back as it was before that change.
- An added section can be undone too: the undo removes it. For other resources, undo refuses a change that created something.
- If the home page changed after that change, through the API or in the dashboard, the undo answers
409 layout_changedand writes nothing. Read the layout and write what you want directly. - A section that comes back after a delete gets a new id.
- The undo is recorded as a change of its own,
undo_change_id, which you can undo in turn. Undoing the undo of a delete answers409 layout_changed, because the section came back with a new id. - Changes the merchant saves in the dashboard are not recorded, so they cannot be undone through the API.
GET /v1/changes?entity=store.home_layoutlists the home layout changes, newest first, withstore:read.- The undo answer does not carry the layout. Read it with a query string of your own, such as
?fresh=<unix time>, so the 30-second cache does not return the layout from before the undo.
The undo needs store:write and an Idempotency-Key.
curl -X POST 'https://api.dzbuild.app/v1/changes/90231/undo' \
-H "Authorization: Bearer $DZ_KEY" \
-H "Idempotency-Key: undo-90231"
{
"data": {
"undone": true,
"change_id": 90231,
"entity": "store.home_layout",
"undo_change_id": 90240
},
"meta": {"request_id": "5ad2f7c01e9b8634", "api_version": "v1"}
}
A change undone a second time answers 409 already_undone.
Errors
| HTTP | Code | Cause |
|---|---|---|
| 400 | bad_request | The body is not a JSON object, a field has the wrong type (type missing, settings not an object, is_active not a boolean, position outside 0 to 24, ids not an array of section ids, version not a string), the section id in the path is not a positive number, or Idempotency-Key is missing or malformed on POST, PATCH or DELETE. |
| 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. |
| 403 | plan_required | The write would leave more sections than the plan allows. The error carries plan and cap. |
| 404 | section_not_found | No section with this id on the page. |
| 409 | write_conflict | Another write landed first, or the version sent on PUT is not the current one. When the error carries sections and version, they are the current layout: retry from them. Otherwise read the layout and retry. |
| 409 | layout_changed | Undo only: the home page changed after this change. |
| 409 | already_undone | Undo only: the change was already undone. |
| 413 | payload_too_large | The body is over 1 MB. |
| 422 | invalid_settings | A value was refused. fields lists every refused setting of the section, such as settings.category. On PUT it lists those of the first refused section only, such as sections.2.settings.layout, and it also covers an id that is not on the page (sections.N.id) and a kept section sent with another type (sections.N.type). message names the paths too. |
| 422 | invalid_section_type | The type does not exist or cannot be added on this theme. fields gives the path. |
| 422 | limit_reached | More than 25 sections, or more of one type than its limit. The error carries limit, except when a PUT sends more than 25 items. |
| 422 | invalid_order | Reorder ids miss a section or repeat one. |
| 422 | no_changes | A PATCH without settings or is_active. |
| 422 | idempotency_key_reuse | The same Idempotency-Key was used with another body. |
| 429 | rate_limited | Too many requests, including more than 30 home layout writes a minute for the store. Wait for Retry-After. |
| 429 | too_many_concurrent | More than 5 home layout 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_settings answer looks like this.
{
"error": {
"code": "invalid_settings",
"message": "Invalid value at settings.category: category not found in this store; accepted values are in settings_schema of GET /v1/store/home-layout",
"fields": [{"path": "settings.category", "code": "invalid"}]
},
"meta": {"request_id": "e4c19a0b7d2f5836", "api_version": "v1"}
}
Limits
- 25 sections per home page (
max_sections). - Plan cap (
cap): 3 sections on Free or an expired plan, 25 from Pro. Hidden sections count. A store above its cap after a downgrade keeps its sections and can still edit, hide, reorder and delete them. A write that leaves more sections than the cap and more than before answers403 plan_required. - Per type: each type has a
limitintypes, 12 forcategory-products. - Settings: 8 KB per section once encoded. Longer text is cut to the setting's
max. - Body: 1 MB.
- Writes: 30 a minute and 5 at once per store, on top of the store's per-minute limit. See Rate limits.