A landing page is a focused, single-product conversion page. They're independent of the storefront catalogue — you can have a landing page with no live product (for upcoming launches), or one tied to a specific product for paid ads.
Sections (image carousels, order forms, fake visitors, countdowns, etc.) can be built through the API with the section endpoints below, or in the dashboard.
Plan limits
Plan | Landing pages (all statuses — drafts count) |
Free | 0 (one-time purchase: 1000 DZD/lifetime each) |
Pro | 3 |
Unlimited / Enterprise | unlimited |
The cap applies on every create path, the API included, and counts every landing page including drafts. POST /v1/landing-pages (and POST /v1/landing-pages/generate) on a store at its limit answers 403 limit_reached. A Free-plan store can create a page only while it has an unused purchased slot; POST /v1/landing-pages marks that page is_purchased: true, which is what makes it visible on the storefront.
GET /v1/landing-pages
List landing pages. Cursor-paginated. Served fresh on every call, like GET /v1/landing-pages/{id}.
Auth: platform key with landing_pages:read, which GET /v1/landing-pages/{id} needs too. A key without it gets 403 forbidden.
Query parameters
Param | Type | Notes |
| int 1–200 | Default 50 |
| string | Opaque |
|
| Filter |
An unrecognised status is ignored, returning all pages rather than a 400.
Response 200
{
"data": {
"items": [
{
"id": 42,
"title": "Black T-Shirt — 30% off",
"slug": "black-tshirt-30-off",
"public_url": "https://your-store.example.com/landing/black-tshirt-30-off",
"status": "active",
"language": "ar",
"product_id": 26,
"views": 1543,
"is_purchased": false,
"created_at": "2026-03-01 10:00:00",
"updated_at": "2026-03-15 14:22:11"
}
],
"next_cursor": null,
"has_more": false
}
}
GET /v1/landing-pages/{id}
Detail with section_count.
{
"data": {
"id": 42,
"title": "Black T-Shirt — 30% off",
"slug": "black-tshirt-30-off",
"public_url": "https://your-store.example.com/landing/black-tshirt-30-off",
"status": "active",
"language": "ar",
"product_id": 26,
"views": 1543,
"is_purchased": false,
"meta_title": "Black T-Shirt — Cotton 200gsm — 30% off | DZBuild",
"meta_description": "Limited-time offer on our cotton black t-shirt.",
"section_count": 7,
"created_at": "2026-03-01 10:00:00",
"updated_at": "2026-03-15 14:22:11"
}
}
Field reference
Field | Notes |
| Strict |
|
|
| Live address of the page: the store's address (its custom domain once it is live, otherwise its subdomain), then |
| The linked product, or |
| Read-only. Counted each time the public page is viewed; the API cannot write it and there is no way to reset it. |
|
|
| Detail endpoint only — a live count of the page's sections, computed per request. |
| SEO tags. See the note under create. |
POST /v1/landing-pages — create
Auth: platform key with landing_pages:write and landing_pages:read. The answer reads the page back, so with landing_pages:write alone the page is saved and the call answers 403 forbidden; the same holds for PATCH and /publish. Requires Idempotency-Key.
Body
Field | Type | Required | Notes |
| string, 1 to 255 bytes | ✅ | The limit counts bytes, not letters: an Arabic letter takes 2 bytes, so an Arabic title tops out near 127 letters |
| string | Auto-derived from | |
|
| Default | |
|
| Default | |
| int | Must belong to your store; the page links to this product | |
| string ≤ 255 | SEO title. Omit it via the API and it is stored and returned as | |
| string | SEO description |
Slugs are made unique within your store by appending -2, -3, … An empty slug base falls back to landing- plus 6 hex characters.
Errors
Code | Cause |
| Wrong Content-Type or malformed JSON |
| Missing or over-long title |
| Cross-store id |
| The store is at its plan's landing page limit (drafts count). On Free: no unused purchased slot is left |
Request
curl -X POST 'https://api.dzbuild.app/v1/landing-pages' \
-H "Authorization: Bearer $DZ_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: $(uuidgen)" \
-d '{
"title": "Black T-Shirt — 30% off",
"language": "ar",
"product_id": 26,
"status": "draft"
}'
Returns 200 (not 201) and the same shape as GET /v1/landing-pages/{id}. The new landing page has no sections: add them with the section endpoints below. The publish check does not run on create, so create the page as a draft and publish it once its sections are in place; a page created with status: active goes live empty.
PATCH /v1/landing-pages/{id}
Partial update.
PATCH validates more strictly than create: an invalid status returns 400 bad_request ("status must be active or draft") and an invalid language returns 400 ("language must be ar, fr, or en") instead of being coerced. title must still be 1 to 255 bytes. A slug sent on PATCH is normalised, unlike on create. Setting status to active runs the publish check (see /publish below). Once a page has a product, product_id: null answers 422 product_required; send another product id to switch products.
curl -X PATCH 'https://api.dzbuild.app/v1/landing-pages/42' \
-H "Authorization: Bearer $DZ_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: $(uuidgen)" \
-d '{ "title": "Black T-Shirt — Spring promo" }'
Renaming auto-regenerates slug only if you didn't pass slug explicitly. When the slug changes, by a rename or explicitly, the old address keeps working and redirects to the new one.
Sections
A page renders its sections from top to bottom. Section reads need landing_pages:read, section writes need landing_pages:write, and every write needs an Idempotency-Key.
Endpoint | Body | Answer |
|
| |
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
A section carries id, section_type, sort_order and settings. Answers that read the page back (the list, reorder and PATCH) also carry created_at and updated_at. The 14 types are image, order_form, order_button, free_text, contact_button, countdown, fake_visitors, special_offer, price_display, product_offers, custom_form, image_carousel, announcement_bar and testimonials. Read GET /v1/landing-page-section-types before writing, so your settings keys match what the page renders.
Settings you send are merged over the type's defaults, so one call can create a fully set up section. On PATCH they are merged over what is stored; send
"replace": trueto start again from the type's defaults. Nested objects merge key by key; lists such asslides,items,offersandfieldsare replaced whole. The section type cannot change.An
order_form,order_buttonorproduct_offerssection needs a product: the page'sproduct_idor its ownsettings.product_id. Without one the call answers422 landing_page_has_no_product. Asettings.product_idfrom another store answers422 validation_error.The order form fields
show_name,show_phoneandshow_wilayaare always saved astruein any section that has them.A
slideslist takes at most 20 entries and anitemslist at most 30 (422 too_many_items). Encoded settings may not pass 262144 bytes (422 settings_too_large). An unknown type answers422 invalid_section_type.On a write, a page outside your store answers
404 landing_page_not_found(the list and the check answer404 not_found), and a section that is not on the page answers404 section_not_found. A reorder list that repeats or leaves out a section answers422 validation_error.Section changes appear in
GET /v1/changes. An edit, a deletion or a reorder can be undone withPOST /v1/changes/{id}/undo, and a deleted section that comes back through an undo gets a new id. Adding a section cannot be undone: delete it instead.
curl -X POST 'https://api.dzbuild.app/v1/landing-pages/42/sections/batch' \
-H "Authorization: Bearer $DZ_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: $(uuidgen)" \
-d '{
"sections": [
{ "type": "announcement_bar" },
{ "type": "order_form" }
]
}'
GET /v1/landing-pages/{id}/check
Reports what a buyer would find broken on the page. Auth: landing_pages:read.
The answer carries landing_page_id, status, product_id, sections (how many were checked), publishable, blocked_by (the first blocking message, or null) and problems. Each problem has code, severity (blocking or warning) and message; problems tied to one section also carry section_id.
Code | Severity | Meaning |
|
| The page has no sections, so it renders blank |
|
| A section takes orders, but neither the page nor the section names a product, so orders would land at 0 DA |
|
| A section points at a product that is not in your store |
|
| Nothing on the page can take an order |
|
| More than one |
|
| A section names a product with variants while the page has no product. Variant pickers render only from the page's own product, so set |
Publishing is refused while a blocking problem remains (see below).
POST /v1/landing-pages/{id}/publish
Convenience: flip status to active. Equivalent to PATCH ... { status: "active" }, and refused the same way: while GET /v1/landing-pages/{id}/check reports a blocking problem, the call answers 422 page_not_publishable and the error carries the problems list. Edits that do not send status skip this check, even on a live page.
curl -X POST 'https://api.dzbuild.app/v1/landing-pages/42/publish' \ -H "Authorization: Bearer $DZ_KEY" \ -H "Idempotency-Key: publish-42-$(date +%s)"
POST /v1/landing-pages/generate
Builds a landing page from one of your products with AI. It answers 202 at once with a task to poll; a run takes about two minutes. Auth: the ai:generate scope. Keys created in the dashboard or with POST /v1/keys and third-party apps do not carry it and get 403 forbidden; Claude, ChatGPT and DZBuild Copilot connections carry it. Requires Idempotency-Key.
Field | Type | Required | Notes |
| string | ✅ | At least 3 characters; cut at 255 |
| int | ✅ | An active product of your store with at least one image |
| string | Brief for the page, cut at 2000 characters | |
|
| Default | |
|
| Default |
The answer carries task_id, size, credits_charged, eta_seconds and poll. Credits are taken when the run starts and given back when the run fails or times out. Errors: 400 bad_request (title missing or shorter than 3 characters), 402 quota_exceeded (not enough AI credits), 403 limit_reached (plan landing page limit, with limit and current), 409 already_processing (one generation per store at a time), 422 product_required, product_not_found or product_has_no_image, 429 rate_limited or too_many_concurrent, 503 provider_unavailable (no credits taken).
Poll GET /v1/landing-pages/generate/{task_id} with landing_pages:read. It answers task_id, status (processing while the page is built, then completed or failed), landing_page_id once the page exists, current_step and error. A run still processing after 10 minutes is marked failed with error timeout at the next poll, and its credits are refunded.
DELETE /v1/landing-pages/{id}
Hard delete. The page's sections are removed with it.
curl -X DELETE 'https://api.dzbuild.app/v1/landing-pages/42' \ -H "Authorization: Bearer $DZ_KEY" \ -H "Idempotency-Key: del-42"
Response: { "data": { "deleted": true, "id": 42 } }.