Skip to main content

Landing pages

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​

PlanLanding pages (all statuses — drafts count)
Free0 (one-time purchase: 1000 DZD/lifetime each)
Pro3
Unlimited / Enterpriseunlimited

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​

ParamTypeNotes
limitint 1–200Default 50
cursorstringOpaque
statusactive | draftFilter

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​

FieldNotes
statusStrict active or draft only — there is no archived state for landing pages.
languagear, fr or en.
public_urlLive address of the page: the store's address (its custom domain once it is live, otherwise its subdomain), then /landing/ and the slug. null while the store has no address. Use it as is rather than building the URL yourself.
product_idThe linked product, or null. An order-taking section (order_form, order_button, product_offers) needs this or its own settings.product_id. Once set, PATCH can switch it to another product but not back to null.
viewsRead-only. Counted each time the public page is viewed; the API cannot write it and there is no way to reset it.
is_purchasedtrue once the page has been bought outright (1000 DZD/lifetime). On the Free plan this is what makes the page visible on the storefront.
section_countDetail endpoint only — a live count of the page's sections, computed per request.
meta_title / meta_descriptionSEO 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​

FieldTypeRequiredNotes
titlestring, 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
slugstringAuto-derived from title if omitted. A slug you supply here is stored without normalisation — send a clean one
statusactive | draftDefault draft. Any other value is silently coerced to draft
languagear | fr | enDefault ar. Any other value is silently coerced to ar
product_idintMust belong to your store; the page links to this product
meta_titlestring ≤ 255SEO title. Omit it via the API and it is stored and returned as null (unlike the dashboard form, which copies title into it). The public page still renders title as a fallback, so the visible page title is correct either way
meta_descriptionstringSEO 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​

CodeCause
bad_request "Body must be valid JSON"Wrong Content-Type or malformed JSON
bad_request "title is required (1-255 chars)"Missing or over-long title
bad_request "product_id N does not belong to this store"Cross-store id
limit_reached (403)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.

EndpointBodyAnswer
GET /v1/landing-page-section-types200: section_types, each a type with its default_settings
GET /v1/landing-pages/{id}/sections200: landing_page_id and sections in render order (up to 500, not paginated)
POST /v1/landing-pages/{id}/sectionssection_type (or type), optional settings201: the new section, added at the end of the page
POST /v1/landing-pages/{id}/sections/batchsections: up to 30 objects with type (or section_type) and optional settings201: the new sections. All or nothing: one bad entry saves none
POST /v1/landing-pages/{id}/sections/reordersections: every section id of the page, once each, in the new order200: the sections in their new order
PATCH /v1/landing-pages/{id}/sections/{section_id}settings, optional replace200: the updated section
DELETE /v1/landing-pages/{id}/sections/{section_id}200: { "deleted": true, "id": 901, "landing_page_id": 42 }

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": true to start again from the type's defaults. Nested objects merge key by key; lists such as slides, items, offers and fields are replaced whole. The section type cannot change.
  • An order_form, order_button or product_offers section needs a product: the page's product_id or its own settings.product_id. Without one the call answers 422 landing_page_has_no_product. A settings.product_id from another store answers 422 validation_error.
  • The order form fields show_name, show_phone and show_wilaya are always saved as true in any section that has them.
  • A slides list takes at most 20 entries and an items list at most 30 (422 too_many_items). Encoded settings may not pass 262144 bytes (422 settings_too_large). An unknown type answers 422 invalid_section_type.
  • On a write, a page outside your store answers 404 landing_page_not_found (the list and the check answer 404 not_found), and a section that is not on the page answers 404 section_not_found. A reorder list that repeats or leaves out a section answers 422 validation_error.
  • Section changes appear in GET /v1/changes. An edit, a deletion or a reorder can be undone with POST /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.

CodeSeverityMeaning
empty_pageblockingThe page has no sections, so it renders blank
order_form_without_productblockingA section takes orders, but neither the page nor the section names a product, so orders would land at 0 DA
section_product_not_foundblockingA section points at a product that is not in your store
no_order_formwarningNothing on the page can take an order
multiple_order_formswarningMore than one order_form or order_button renders
variants_need_page_productwarningA section names a product with variants while the page has no product. Variant pickers render only from the page's own product, so set product_id on the page

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.

FieldTypeRequiredNotes
titlestring✅At least 3 characters; cut at 255
product_idint✅An active product of your store with at least one image
descriptionstringBrief for the page, cut at 2000 characters
languagear | fr | enDefault ar
sizemedium | tallDefault medium. medium costs 20 AI credits, tall costs 45

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 } }.

This page for AI toolsView as MarkdownOpen in ChatGPTOpen in Claude