Skip to main content

Promo codes

These four endpoints manage the store's promo codes, the codes buyers type at checkout to lower what they pay. They work on the same codes as the promo codes page of the dashboard, described in Discount codes, and they can also rename a code, which the dashboard cannot.

A code takes its discount off the products subtotal, never off shipping. A percentage code takes that share of the subtotal. A fixed code subtracts its amount in DZD and never takes off more than the subtotal. A code cannot be limited to a product, a category or a customer, cannot cap the discount on a large cart, and cannot give free shipping.

Before you start​

  • The Promo codes add-on must be active on the store (/dashboard/addons). The list works without it and tells you in addon_enabled. Every write answers 409 addon_inactive until the add-on is on, and buyers cannot use any code at checkout while it is off.
  • The key needs the promo scopes. Keys created from the dashboard (/dashboard/api) get both. Scopes are frozen when a key is created, so a key that lacks them answers 403 forbidden: create a new key from the dashboard.
  • starts_at and expires_at are in Algeria time, in the form YYYY-MM-DD HH:MM:SS.
ScopeDescription
promos:readRead promo codes.
promos:writeCreate, update and delete promo codes. This changes the prices your buyers pay.

The promo code object​

FieldTypeMeaning
idintThe code's id in the store.
codestringWhat buyers type. Upper case, A-Z, 0-9, - and _, unique in the store.
discount_typestringpercentage or fixed.
discount_valuenumberThe percentage (above 0, at most 100) or the amount in DZD.
min_order_amountnumber or nullThe products subtotal an order must reach for the code to apply. null means no minimum.
max_usesint or nullHow many orders can use the code. null means unlimited.
used_countintHow many orders have used it. Read-only.
is_activeboolAn inactive code is refused at checkout.
starts_atstring or nullBefore this time the code is refused. null means it works right away.
expires_atstring or nullAfter this time the code is refused. null means it never expires.
created_at, updated_atstringYYYY-MM-DD HH:MM:SS, server time.

GET /v1/promo-codes​

The store's codes, newest first. There is no call that reads a single code by id: page through this list.

Auth: platform key with promos:read.

Query parameters​

ParamTypeDefaultNotes
is_activestringnonetrue, 1, yes or on returns the active codes. Any other value returns the inactive ones. Leave it out for all codes.
limitint501 to 200.
cursorstringnonenext_cursor of the previous page. See Pagination.

Request​

curl 'https://api.dzbuild.app/v1/promo-codes?is_active=true' \
-H "Authorization: Bearer $DZ_KEY"

Response 200​

{
"data": {
"items": [
{
"id": 20,
"code": "WELCOME10",
"discount_type": "percentage",
"discount_value": 10,
"min_order_amount": 3000,
"max_uses": 100,
"used_count": 7,
"is_active": true,
"starts_at": null,
"expires_at": "2026-11-30 23:59:00",
"created_at": "2026-10-06 14:20:11",
"updated_at": "2026-10-06 14:20:11"
}
],
"next_cursor": null,
"has_more": false,
"addon_enabled": true
}
}

addon_enabled says whether the Promo codes add-on is active. When it is false, the list still answers, but writes fail and buyers cannot use the codes.

POST /v1/promo-codes​

Creates a code. It works at checkout as soon as it is created, unless you send is_active: false or a later starts_at.

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

Body​

FieldTypeRequiredNotes
codestringyesSpaces around it are removed and letters are upper-cased, then it must be 2 to 30 characters of A-Z, 0-9, - or _. A code that already exists in the store answers 409 code_exists.
discount_typestringyespercentage or fixed, in any case. There is no default.
discount_valuenumberyesAbove 0. At most 100 for percentage, and 100 needs the merchant's confirmation (see the section on 100% discounts below). No upper limit for fixed.
min_order_amountnumber or nullnoMinimum products subtotal in DZD. 0, "" or null means no minimum.
max_usesint or nullno1 or more. 0, "" or null means unlimited.
is_activeboolnoDefaults to true. Send a JSON boolean: a string such as "false" is read as true.
starts_atstring or nullnoA common date-time form, such as 2026-11-01 08:00 or an ISO 8601 string. Stored as YYYY-MM-DD HH:MM:SS in Algeria time; a value with a UTC offset is converted. null or "" means no start date.
expires_atstring or nullnoSame forms. It must be in the future and after starts_at. null or "" means no expiry.
confirm_tokenstringnoOnly for a 100% discount.
confirm_full_discountboolnoOnly for a 100% discount.

Request​

curl -X POST 'https://api.dzbuild.app/v1/promo-codes' \
-H "Authorization: Bearer $DZ_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: promo-welcome10-create" \
-d '{"code": "welcome10", "discount_type": "percentage", "discount_value": 10, "min_order_amount": 3000, "max_uses": 100, "expires_at": "2026-11-30 23:59"}'

Response 201​

{
"data": {
"id": 20,
"code": "WELCOME10",
"discount_type": "percentage",
"discount_value": 10,
"min_order_amount": 3000,
"max_uses": 100,
"used_count": 0,
"is_active": true,
"starts_at": null,
"expires_at": "2026-11-30 23:59:00",
"created_at": "2026-10-06 14:20:11",
"updated_at": "2026-10-06 14:20:11"
}
}

PATCH /v1/promo-codes/{id}​

Changes only the fields you send, with the same rules as the create. A field you leave out keeps its value.

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

  • code can be changed. The new text must be free in the store, otherwise the call answers 409 code_exists.
  • discount_type and discount_value are checked as a pair. Sending only discount_type: "percentage" on a 500 DZD fixed code answers 422 invalid_discount_value, because 500 is above 100. Send both.
  • A new expires_at must be in the future. A code that has already expired stays editable as long as you do not send expires_at.
  • used_count cannot be written.
  • An empty body changes nothing and returns the code.
  • A code of another store answers 404, like a code that does not exist.

Request​

curl -X PATCH 'https://api.dzbuild.app/v1/promo-codes/20' \
-H "Authorization: Bearer $DZ_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: promo-20-extend" \
-d '{"max_uses": 200, "expires_at": "2026-12-31 23:59"}'

Response 200​

{
"data": {
"id": 20,
"code": "WELCOME10",
"discount_type": "percentage",
"discount_value": 10,
"min_order_amount": 3000,
"max_uses": 200,
"used_count": 7,
"is_active": true,
"starts_at": null,
"expires_at": "2026-12-31 23:59:00",
"created_at": "2026-10-06 14:20:11",
"updated_at": "2026-10-20 09:05:42"
}
}

DELETE /v1/promo-codes/{id}​

Deletes the code, so buyers can no longer use it. Orders already placed with it keep their discount. To pause a code without losing its usage count, send is_active: false with PATCH instead.

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

Request​

curl -X DELETE 'https://api.dzbuild.app/v1/promo-codes/20' \
-H "Authorization: Bearer $DZ_KEY" \
-H "Idempotency-Key: promo-20-delete"

Response 200​

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

100% discounts​

A percentage code at 100 makes the goods free for every buyer who has the code. A create, or an update that sends discount_type or discount_value, is not written on the first call when the result is a 100% percentage code:

  1. The call answers 422 confirmation_required and writes nothing. The error carries confirm_token (single use, valid 600 seconds), action and will_change, a summary to show the merchant.
  2. Once the merchant approves, repeat the same body with confirm_token added and a new Idempotency-Key (the first key is bound to the body without the token). A token that expired, was already used, or no longer matches the request answers 422 confirmation_stale with a fresh token.

With a key created from the dashboard you can skip the round trip by sending confirm_full_discount: true in the first call. The DZBuild Copilot cannot use that flag and always goes through the token.

{
"error": {
"code": "confirmation_required",
"message": "A 100% discount makes every order free ...",
"confirm_token": "cft_xxxxxxxxxxxxxxxx",
"confirm_token_expires_in": 600,
"action": "promo.full_discount:new",
"will_change": {
"action": "Create a promo code that makes orders free",
"code": "FREEGIFT",
"discount": "100% off the whole order subtotal",
"reversible": true,
"note": "Any customer with this code pays 0 for the goods. Orders already placed with it cannot be reversed by deleting the code."
}
}
}

On an update, action ends with the code's id instead of new, and will_change.action reads Change promo code #20 to make orders free.

curl -X POST 'https://api.dzbuild.app/v1/promo-codes' \
-H "Authorization: Bearer $DZ_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: promo-freegift-approved" \
-d '{"code": "FREEGIFT", "discount_type": "percentage", "discount_value": 100, "max_uses": 1, "confirm_token": "cft_REPLACE_WITH_TOKEN"}'

Change history and undo​

Every create, update and delete made through the API is recorded. Codes changed on the dashboard page are not recorded, so they cannot be undone through the API.

  • GET /v1/changes?entity=promo_code lists the promo code changes, newest first, with store:read. Each item carries the change id, the code's id as entity_id, the action (create, update or delete) and a summary.
  • POST /v1/changes/{id}/undo needs promos:write, an Idempotency-Key and the add-on active, like any write.
  • Undoing an update puts the earlier values back, with no confirmation, even a 100% discount or an expiry date that has passed. If the code was deleted since, the undo answers 422 restore_target_missing.
  • Undoing a delete re-creates the code with its usage count, and keeps its id when that id is still free.
  • Undoing a create answers 422 nothing_to_restore: delete the code instead.
  • An installed app's token cannot read or undo changes: both answer 403 forbidden.
curl -X POST 'https://api.dzbuild.app/v1/changes/500/undo' \
-H "Authorization: Bearer $DZ_KEY" \
-H "Idempotency-Key: undo-500"
{
"data": {
"undone": true,
"change_id": 500,
"entity": "promo_code",
"undo_change_id": 510
}
}

A change undone a second time answers 409 already_undone.

Errors​

HTTPCodeCause
400bad_requestThe id in the path is not all digits, the body is not valid JSON, or Idempotency-Key is missing or malformed.
403forbiddenMissing scope: promos:read or Missing scope: promos:write, or API access requires an active Enterprise plan for a merchant key whose store is not on an active Enterprise plan.
404not_foundNo promo code with this id in the store.
409addon_inactiveThe Promo codes add-on is not active on the store. Writes only.
409code_existsAnother code of the store already has this text.
422invalid_codecode is shorter than 2 or longer than 30 characters, or has a character other than A-Z, 0-9, - and _.
422invalid_discount_typediscount_type is missing, or is not percentage or fixed.
422invalid_discount_valuediscount_value is missing, not a number, 0 or less, or above 100 for percentage.
422invalid_min_order_amountmin_order_amount is not a number.
422invalid_max_usesmax_uses is not a number, or is below 1.
422invalid_starts_at, invalid_expires_atThe date cannot be read.
422expires_at_in_pastThe expires_at you sent is not in the future.
422invalid_date_windowexpires_at is not after starts_at.
422confirmation_required, confirmation_staleA 100% discount waits for the merchant's approval. See the section above.
422idempotency_key_reuseThe same Idempotency-Key was used with a different body or path.
500server_errorThe write failed. Retry with the same Idempotency-Key.

A 4xx answer is stored with its Idempotency-Key for 24 hours and replayed to a retry with the same body. After you activate the add-on or fix the body, send the call with a new key. Errors that every endpoint shares, such as 401, 402 and 429, are in Errors, and the retry rules are in Idempotency.

Known limits​

  • Uses can pass max_uses. When several buyers check out with the same code at the same moment, used_count can end above max_uses.
  • No webhook. Creating, changing or deleting a promo code sends no webhook event.
This page for AI toolsView as MarkdownOpen in ChatGPTOpen in Claude