Aller au contenu principal

Products

The product is the core sellable unit on a store. All product calls are scoped to the calling key's store — you can never accidentally touch another merchant's data.

GET /v1/products

List products. Cursor-paginated. Cached at the edge for 30 s.

Auth: platform key with products:read.

Query parameters

ParamTypeDefaultNotes
limitint (1–200)50Page size
cursorstringFrom a prior response's next_cursor
statusactive | draft | archivedFilter by status
searchstringMatch against name (LIKE) and exact sku

Request

curl 'https://api.dzbuild.app/v1/products?limit=10&status=active' \
-H "Authorization: Bearer $DZ_KEY"

Response 200

{
"data": {
"items": [
{
"id": 26,
"name": "PRO",
"slug": "pro",
"short_description": null,
"price": 1000,
"compare_price": null,
"sku": "",
"stock_quantity": 0,
"track_stock": false,
"status": "active",
"has_variants": true,
"featured": false,
"primary_image": "13_1768313552_b33d660c_1562f6687591.webp",
"created_at": "2026-01-13 15:06:06",
"updated_at": "2026-01-13 15:12:32"
}
],
"next_cursor": null,
"has_more": false
},
"meta": { "request_id": "...", "api_version": "v1" }
}

primary_image is a filename. Resolve to a URL with https://cdn.dzbuild.app/<store_id>/products/<filename> (or your custom CDN domain). The full path is in images[].url on GET /v1/products/{id}.

GET /v1/products/{id}

Full product detail including images and variants.

Auth: platform key with products:read.

Request

curl https://api.dzbuild.app/v1/products/26 \
-H "Authorization: Bearer $DZ_KEY"

Response 200

{
"data": {
"id": 26,
"name": "PRO",
"slug": "pro",
"description": "- Single store\n- Up to 300 products\n- ...",
"short_description": null,
"category_id": null,
"pricing": {
"price": 1000,
"compare_price": null,
"cost_price": null
},
"inventory": {
"sku": "",
"barcode": null,
"track_stock": false,
"stock_quantity": 0,
"low_stock_alert": 5
},
"shipping": {
"weight": null, "height": null, "width": null, "length": null,
"do_insurance": false
},
"status": "active",
"featured": false,
"has_variants": true,
"images": [
{ "id": 28, "url": "13_1768313552_b33d660c_1562f6687591.webp",
"is_primary": true, "sort_order": 0 }
],
"variants": [
{
"id": 11,
"name": "Duration",
"type": "text",
"options": [
{ "id": 14, "value": "30 days", "color_code": null, "image_id": null, "stock": null },
{ "id": 15, "value": "90 days", "color_code": null, "image_id": null, "stock": null },
{ "id": 16, "value": "180 days", "color_code": null, "image_id": null, "stock": null },
{ "id": 17, "value": "365 days", "color_code": null, "image_id": null, "stock": null }
]
}
],
"created_at": "2026-01-13 15:06:06",
"updated_at": "2026-01-13 15:12:32"
}
}

POST /v1/products — create

Auth: platform key with products:write. Requires Idempotency-Key.

Body

FieldTypeRequiredNotes
namestring (1–255)
pricenumber ≥ 0DZD
compare_pricenumber ≥ 0 | nullStrike-through price
cost_pricenumber ≥ 0 | nullInternal only — never shown to customers
descriptionstringLong-form, can include line breaks
short_descriptionstring ≤ 500One-liner
skustring ≤ 100Internal SKU
barcodestring ≤ 100UPC/EAN
weightnumberkg, for shipping
shipping_height / width / lengthnumbercm
do_insuranceboolForce shipping insurance on this item
track_stockboolDefault false
stock_quantityint ≥ 0If track_stock
low_stock_alertint ≥ 0Default 5. Triggers product.stock_low webhook.
variant_stock_enabledboolPer-variant SKUs
combination_stock_enabledboolPer (color × size) combo SKUs. Implies variant_stock_enabled.
category_idintMust exist in your store
statusactive | draft | archivedDefault draft
featuredboolDefault false

When variant_stock_enabled or combination_stock_enabled is true, track_stock is auto-disabled (variants own their own stock).

Plan limit

Free: 5 active products. Pro: 300. Unlimited / Enterprise: unlimited. The check counts only status = 'active' rows. Drafts don't count. Hitting the limit returns:

{ "error": { "code": "bad_request",
"message": "Plan 'free' allows at most 5 active products. Upgrade to add more." } }

Request

curl -X POST 'https://api.dzbuild.app/v1/products' \
-H "Authorization: Bearer $DZ_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: $(uuidgen)" \
-d '{
"name": "T-shirt - Cotton 200gsm",
"price": 1500,
"compare_price": 1900,
"description": "100% cotton, made in Algeria.",
"sku": "TS-COT-200",
"stock_quantity": 50,
"track_stock": true,
"status": "draft"
}'

Response 201

Echoes the same shape as GET /v1/products/{id}. id, slug, and created_at are now populated. slug is auto-generated from name and made unique within your store. You can override by passing your own slug in the body (subject to [a-z0-9-] normalisation).

Errors

CodeCause
bad_request "Body must be valid JSON"Wrong Content-Type or malformed JSON
bad_request "name is required (1-255 chars)"Missing or over-long name
bad_request "price must be a non-negative number"Bad price
bad_request "category_id N does not belong to this store"Cross-store id
bad_request "Plan 'free' allows at most …"Plan limit

PATCH /v1/products/{id} — update

Auth: platform key with products:write. Requires Idempotency-Key.

Partial update — send only the fields you want to change. Unspecified fields are preserved.

curl -X PATCH 'https://api.dzbuild.app/v1/products/26' \
-H "Authorization: Bearer $DZ_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: $(uuidgen)" \
-d '{ "price": 1200, "status": "active" }'

Returns 200 and the full updated product. If the product doesn't exist (or belongs to another store) you get 404 not_found.

Renaming via PATCH { name: ... } automatically regenerates the slug only if you didn't pass slug explicitly. Pass slug if you want to preserve a specific URL after a rename.

DELETE /v1/products/{id}

Auth: platform key with products:write. Requires Idempotency-Key.

curl -X DELETE 'https://api.dzbuild.app/v1/products/26' \
-H "Authorization: Bearer $DZ_KEY" \
-H "Idempotency-Key: del-26-2026-04-30"

Response:

{ "data": { "deleted": true, "id": 26 } }

This is a hard delete — the product row is removed, and FK cascades clean up images, variants, offers, and combinations. Image files in R2 are left for an async cleanup sweep (we don't block the API call on R2 deletion).

POST /v1/products/{id}/images (coming v1.1)

Will return a presigned R2 PUT URL so your client uploads files directly to Cloudflare R2 — no Algerian-origin round-trip, no FPM-blocking image processing.

Until then, image uploads go through the dashboard at Products → Edit → Images.