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, fake visitors, countdowns, etc.) are managed in the dashboard at v1; the API CRUDs the parent record only. A v1.1 update will expose section CRUD too.

Plan limits

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

The cap is enforced by the dashboard create/duplicate flows only, counting every landing page including drafts. The API enforces nothing: POST /v1/landing-pages followed by /publish bypasses the cap entirely, and on a paid plan the extra pages render live on the storefront. Free-plan pages stay invisible unless the page was purchased (is_purchased).

GET /v1/landing-pages

List landing pages. Cursor-paginated. Cached for 30 s — check the X-Cache: HIT|MISS response header. GET /v1/landing-pages/{id} is not cached.

Auth: any active platform key for the store (landing_pages:read is not enforced at v1; only landing_pages:write is checked, on the write endpoints).

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",
"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",
"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.
product_idThe linked product, or null. A page with no product id cannot price its order form correctly.
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. Requires Idempotency-Key.

Body

FieldTypeRequiredNotes
titlestring 1–255
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

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 zero sections — populate them in the dashboard.

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–255 characters. A slug sent on PATCH is normalised, unlike on create.

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.

POST /v1/landing-pages/{id}/publish

Convenience: flip status to active. Equivalent to PATCH ... { status: "active" }.

curl -X POST 'https://api.dzbuild.app/v1/landing-pages/42/publish' \
-H "Authorization: Bearer $DZ_KEY" \
-H "Idempotency-Key: publish-42-$(date +%s)"

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