These endpoints cover what the dashboard keeps on its shipping pages: the home and desk delivery price of each wilaya, the free-shipping rules, and the couriers linked to the store. They also serve the wilaya and commune lists a checkout form needs, and the communes and stop desks the store's courier serves.
Prices are in DZD. POST /v1/orders prices delivery from these rates and ignores any shipping cost sent in the body, so a custom storefront reads the rates to show an estimate and lets the order compute the charge. See Custom themes & storefronts.
Before you start
The key needs the shipping scopes. Keys created from the dashboard (Settings → API,
/dashboard/api) have both. Scopes are frozen when a key is created, so an older key that lacks them answers403 forbidden: create a new key from the dashboard.A store that sells digital products has no shipping setup: every write on this page answers
422 shipping_not_availablethere.Every write needs an
Idempotency-Keyheader. See Idempotency.Testing and linking a courier call the courier's servers during the request, and a rate sync calls them in the background. These three calls and
POST /v1/orders/{id}/send-to-deliveryshare a per-store courier budget on top of the rate limits.Rate and settings writes can be undone. Linking, unlinking and changing the default courier cannot. The undo section at the end of this page explains how.
Shipping reads are not cached at the edge: a
GETsent right after a write returns the new values.
Scope | Description |
| Read shipping rates and settings, linked couriers, courier coverage and the wilaya and commune lists. |
| Change shipping rates and settings, and link, test, unlink or sync couriers. |
GET /v1/wilayas
The wilayas the store delivers to, following its wilaya mode: 1 to 58 in the courier-compatible mode, 1 to 69 in 69-wilaya mode. Names come in Arabic, French and English. The whole list comes in one answer, without pagination.
Auth: platform key with shipping:read.
Request
curl 'https://api.dzbuild.app/v1/wilayas' \ -H "Authorization: Bearer $DZ_KEY"
Response 200
Two of the 58 wilayas are shown. mode_note is one English sentence about the mode.
{
"data": {
"wilaya_mode": "58",
"mode_note": "Courier-compatible mode: wilayas 1-58 only.",
"count": 58,
"wilayas": [
{ "id": 1, "name_ar": "أدرار", "name_fr": "Adrar", "name_en": "Adrar" },
{ "id": 16, "name_ar": "الجزائر", "name_fr": "Alger", "name_en": "Algiers" }
]
}
}
GET /v1/wilayas/{id}/communes
The communes of one wilaya, sorted by French name. Any wilaya from 1 to 69 is answered, whatever the store's wilaya mode. The whole list comes in one answer.
Auth: platform key with shipping:read.
This is the platform's own commune list. It does not say which communes a courier serves: GET /v1/shipping/coverage does.
Request
curl 'https://api.dzbuild.app/v1/wilayas/16/communes' \ -H "Authorization: Bearer $DZ_KEY"
Response 200
Two of the 57 communes of wilaya 16 are shown.
{
"data": {
"wilaya_id": 16,
"count": 57,
"communes": [
{ "id": 564, "wilaya_id": 16, "name_ar": "عين بنيان", "name_fr": "Ain Benian" },
{ "id": 558, "wilaya_id": 16, "name_ar": "عين طاية", "name_fr": "Ain Taya" }
]
}
}
An id that is not all digits answers 400 bad_request. A wilaya that does not exist answers 404 not_found.
GET /v1/shipping/rates
The delivery price of every wilaya the store has a rate for, keyed by wilaya id. A wilaya without a rate is absent from rates.
Auth: platform key with shipping:read.
Request
curl 'https://api.dzbuild.app/v1/shipping/rates' \ -H "Authorization: Bearer $DZ_KEY"
Response 200
One wilaya is shown.
{
"data": {
"wilaya_mode": "58",
"currency": "DZD",
"limits": {
"max_price": 100000,
"max_delivery_days": 60
},
"count": 58,
"rates": {
"16": {
"home_price": 400,
"home_enabled": true,
"desk_price": 300,
"desk_enabled": true,
"days": 1,
"is_active": true,
"synced_provider": null,
"synced_at": null
}
}
}
}
Field | Meaning |
|
|
| The highest price and the highest |
| Number of wilayas in |
| Price of home delivery and of desk delivery, in DZD. |
| Whether the store offers that delivery type in this wilaya. |
| Delivery time in days. |
| The courier whose price list last wrote this rate, and when ( |
POST /v1/shipping/rates
Creates or changes the rates of the wilayas you send. The other wilayas are not touched.
Auth: platform key with shipping:write. Requires Idempotency-Key.
Body
rates is an object keyed by wilaya id, written as plain digits ("16", not "016"), with 1 to 69 wilayas. Each value holds the fields to set, and every field is optional.
Field | Type | Notes |
| number | DZD, rounded to two decimals, 0 to 100000. A numeric string is accepted. |
| bool |
|
| number | Same rules as |
| bool | Same rules as |
| int | A whole number of days, 0 to 60. |
A field you leave out, or send as null, keeps its stored value. A wilaya that had no rate starts from price 0, both delivery types on and 3 days.
Request
curl -X POST 'https://api.dzbuild.app/v1/shipping/rates' \
-H "Authorization: Bearer $DZ_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: rates-2026-10-06-1" \
-d '{"rates": {"16": {"home_price": 450, "desk_price": 350}, "31": {"desk_enabled": false}}}'
Response 200
rates holds the written wilayas only, as stored after the write.
{
"data": {
"updated": 2,
"wilaya_ids": [16, 31],
"rates": {
"16": { "home_price": 450, "home_enabled": true, "desk_price": 350, "desk_enabled": true, "days": 1 },
"31": { "home_price": 500, "home_enabled": true, "desk_price": 350, "desk_enabled": false, "days": 2 }
}
}
}
The prior values are saved, so the write can be undone. The answer does not carry a change_id: see the undo section.
GET /v1/shipping/settings
The free-shipping rules and the wilaya mode.
Auth: platform key with shipping:read.
Request
curl 'https://api.dzbuild.app/v1/shipping/settings' \ -H "Authorization: Bearer $DZ_KEY"
Response 200
The answer also carries a notes object with two English sentences that restate the threshold rule and the wilaya mode rule. The example leaves it out.
{
"data": {
"free_shipping": false,
"free_shipping_threshold": 8000,
"free_shipping_threshold_active": true,
"wilaya_mode": "58"
}
}
Field | Meaning |
|
|
| Order subtotal in DZD from which delivery is free. |
|
|
|
|
PATCH /v1/shipping/settings
Changes one or more of the three settings. Send at least one; other fields are ignored.
Auth: platform key with shipping:write. Requires Idempotency-Key.
Body
Field | Type | Notes |
| bool |
|
| number or null | DZD, rounded to two decimals, 0 to 99999999.99. |
| string | Only |
Request
curl -X PATCH 'https://api.dzbuild.app/v1/shipping/settings' \
-H "Authorization: Bearer $DZ_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: settings-2026-10-06-1" \
-d '{"free_shipping_threshold": 8000}'
Response 200
The settings after the write, without notes. The prior values are saved, so the change can be undone.
GET /v1/shipping/providers
Every courier the platform supports, linked to the store or not, with what each one asks for when you link it. Credential values are never returned: has_id and has_token only say whether one is stored. The whole list comes in one answer.
Auth: platform key with shipping:read.
Request
curl 'https://api.dzbuild.app/v1/shipping/providers' \ -H "Authorization: Bearer $DZ_KEY"
Response 200
One courier is shown. The answer also carries a note sentence, left out here.
{
"data": {
"count": 103,
"providers": [
{
"provider": "yalidine",
"family": "yalidine",
"credentials": {
"api_id": { "label": "API ID", "required": true },
"api_token": { "label": "API Token", "required": true },
"_note": "Send an empty string to keep the currently stored value. Credentials are never returned by this API."
},
"extra_fields": {
"delivery_tier": {
"label": "Service tier",
"required": false,
"values": ["express"],
"note": "Only \"express\" is valid here: this courier rejects the economic parameter outright and every order push would fail."
}
},
"supports_rate_sync": true,
"linked": true,
"source": "store_delivery_providers",
"is_enabled": true,
"is_default": true,
"is_send_default": true,
"has_id": true,
"has_token": true,
"delivery_tier": "express",
"economic_available": null,
"credentials_failed_at": null,
"synced_tier": "express",
"stock_account": null,
"auto_validate": null,
"custom_name": null,
"linked_at": "2026-09-14 10:12:00",
"updated_at": "2026-09-14 10:12:00"
}
]
}
}
Field | Meaning |
|
|
| What |
| The other fields this courier accepts when you link it, keyed by name. An empty array when there are none. |
| Whether |
| Whether the store has this courier. |
|
|
| Whether the link is turned on. |
| Whether this is the store's default courier. |
| The courier |
| Yalidine-family service tier: the one chosen, the one the last rate sync used, and whether the account offered the economic tier at that sync. |
| The extra fields stored for this courier, |
| ISO 8601 time, set when the courier kept refusing the stored credentials. Sends to this courier are refused while it is set. Linking the courier again clears it. |
|
|
What api_id and api_token hold
|
|
|
| API ID | API Token |
| Token | Key |
| API Key (secret key) | Tenant ID |
| API Token | User GUID |
| Public Key | Bearer Token |
| API Key | API Token |
| ApiKey | ApiSecret |
| API Token | none |
| Bearer Token | none |
| API Key | none |
| x-api-key | none |
| Bearer Token | none |
Extra fields, all optional unless stated:
delivery_tier, Yalidine family:express.guepexalso takeseconomic.stock_account,ecotrackfamily: fulfil orders from the courier's stock.auto_validate,noest: validate orders automatically at the courier.api_urlandcustom_name,customecotrack, both required to link: the courier's https Ecotrack address (a host ending in.ecotrack.dz, orplatform.dhd-dz.comorapp.conexlog-dz.com) and the name to show for it, up to 100 characters.
POST /v1/shipping/providers/test
Sends credentials to the courier and reports whether it accepted them. Nothing is saved. An empty or missing api_id or api_token uses the value stored for this courier, so you can re-test a linked courier without holding its credentials.
Auth: platform key with shipping:write. Requires Idempotency-Key. Counts against the courier budget.
Body
Field | Type | Required | Notes |
| string | yes | A slug from |
| string | unless stored | First credential. |
| string | unless stored | Second credential, for couriers that take two. |
| string |
| The courier's Ecotrack address. When the stored credentials are reused, it must match the stored address. |
| string | no |
|
Request
curl -X POST 'https://api.dzbuild.app/v1/shipping/providers/test' \
-H "Authorization: Bearer $DZ_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: test-yalidine-1" \
-d '{"provider": "yalidine", "api_id": "YOUR_API_ID", "api_token": "YOUR_API_TOKEN"}'
Response 200
A courier that refuses the credentials also answers 200, with ok: false and the courier check's message. resolved_provider is set for zrexpressnew only: the ZR Express platform that accepted the pair.
{
"data": {
"provider": "yalidine",
"ok": true,
"message": "تم الاتصال بنجاح",
"resolved_provider": null,
"saved": false
}
}
POST /v1/shipping/providers
Links a courier, or re-saves a linked one. The platform tests the credentials with the courier first and saves nothing when the courier refuses them.
Auth: platform key with shipping:write. Requires Idempotency-Key. Counts against the courier budget.
Body
The fields of the test call, plus:
Field | Type | Default | Notes |
| bool |
| Turn the link on or off. |
| bool |
| Make this courier the store's default. |
| string | none |
|
| bool | stored value |
|
| bool | stored value |
|
An empty
api_idorapi_tokenkeeps the stored value, so a linked courier can be re-saved without sending its credentials again.The first courier a store links becomes its default. In the answer,
is_defaultistrueonly when this call made the courier the default, so a default courier re-saved withoutset_defaultstays the default while the answer saysfalse.GET /v1/shipping/providersshows the real state.zrexpressandzrexpressneware one courier: linking one replaces the other. Azrexpressnewpair accepted by the older ZR Express platform is saved aszrexpress, andproviderin the answer says so.
Request
curl -X POST 'https://api.dzbuild.app/v1/shipping/providers' \
-H "Authorization: Bearer $DZ_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: link-yalidine-1" \
-d '{"provider": "yalidine", "api_id": "YOUR_API_ID", "api_token": "YOUR_API_TOKEN", "set_default": true}'
Response 200
{
"data": {
"provider": "yalidine",
"is_enabled": true,
"is_default": true,
"has_id": true,
"has_token": true,
"undoable": false,
"note": "Courier credentials are never recorded, so linking cannot be undone. To revert, link the previous courier again or unlink this one."
}
}
A refused credential answers 422 credentials_rejected with the courier's message, and nothing is saved.
POST /v1/shipping/providers/default
Makes a linked courier the store's default. New sends to delivery go to it.
Auth: platform key with shipping:write. Requires Idempotency-Key.
provider in the body names the courier. Only a courier linked from the courier list can be made the default; a courier set in the store settings answers 404 provider_not_linked. When the chosen courier is turned off, warning says that sends stay off until it is turned on again.
Request
curl -X POST 'https://api.dzbuild.app/v1/shipping/providers/default' \
-H "Authorization: Bearer $DZ_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: default-noest-1" \
-d '{"provider": "noest"}'
Response 200
{
"data": {
"provider": "noest",
"is_default": true,
"previous_default": "yalidine",
"undoable": false,
"warning": "New send-to-delivery pushes now go to \"noest\". "
}
}
Confirmation for a rate sync or an unlink
A rate sync overwrites the merchant's own prices and an unlink removes stored credentials, so both calls ask for a confirmation before they act.
Call without a confirmation. The answer is
422 confirmation_required, and theerrorobject addsconfirm_token(single use),confirm_token_expires_in(600seconds),actionandwill_change, the summary to show the merchant.Once the merchant approves, repeat the call with
confirm_tokenin the body and a newIdempotency-Key. The first key is bound to the body without the token, so reusing it answers422 idempotency_key_reuse.
A token works once, only for the key that received it, and only while what it describes is unchanged: the rate table for a sync, and for an unlink the number of linked couriers and whether this one is the default. A token that was used, expired or no longer matches answers 422 confirmation_stale with a fresh token and summary.
A key that is not used by the in-dashboard assistant may send "confirm": true instead of a token and skip step 1. Keys used by the assistant must send the token.
A sync's first answer looks like this.
{
"error": {
"code": "confirmation_required",
"message": "Syncing overwrites your own prices for every wilaya \"yalidine\" serves. Show the merchant the summary below; when they approve, re-send with the confirm_token.",
"confirm_token": "cft_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
"confirm_token_expires_in": 600,
"action": "shipping.rates_sync:yalidine",
"will_change": {
"action": "Overwrite shipping rates from yalidine",
"wilayas_at_risk": 58,
"reversible": true,
"note": "The prior prices are saved to the change log first, so this can be undone."
}
}
}
For an unlink, will_change holds action, was_store_default, remaining_providers, consequence, reversible (false) and note.
POST /v1/shipping/rates/sync
Replaces the store's prices with the courier's own price list, for each wilaya from 1 to 58 the courier prices. The sync runs in the background. Before anything is queued, the whole rate table is saved, and change_id in the answer undoes the sync.
Auth: platform key with shipping:write. Requires Idempotency-Key and a confirmation. Counts against the courier budget.
Body
Field | Type | Required | Notes |
| string | yes | A courier linked from the courier list and turned on. |
| string | see above | From the |
| bool | see above |
|
What the sync changes
It writes
home_priceanddesk_priceand setssynced_providerandsynced_at. The on/off switches anddaysof a wilaya that already had a rate stay as they were. A wilaya that had no rate gets3days.Yalidine-family couriers need the store's wilaya, set with
wilaya_idinPATCH /v1/store(see Store). Without it the call answers422 store_wilaya_required.Follow the result with
GET /v1/shipping/rates: the rates the sync wrote name the courier insynced_providerand carry a newsynced_at. When the courier sends no prices, the rates stay as they were.While a sync of the same courier is running, the call answers
202withstatus: already_running, that sync'ssync_idandchange_id: null, without asking for a confirmation.After a sync succeeds, the same courier can be synced again 5 minutes later. An earlier call answers
429 sync_cooldown.
Request
curl -X POST 'https://api.dzbuild.app/v1/shipping/rates/sync' \
-H "Authorization: Bearer $DZ_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: sync-yalidine-2" \
-d '{"provider": "yalidine", "confirm_token": "cft_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"}'
Response 202
{
"data": {
"status": "queued",
"sync_id": "a1b2c3d4e5f6a7b8c9d0e1f2",
"change_id": 500,
"note": "The sync runs in the background and overwrites your prices for every wilaya this courier serves. Poll GET /v1/shipping/rates for the result; undo change_id to restore the prior prices."
}
}
DELETE /v1/shipping/providers/{provider}
Unlinks a courier and removes its credentials from the store. This cannot be undone: to send with that courier again, link it again. Parcels already at the courier keep being tracked.
Auth: platform key with shipping:write. Requires Idempotency-Key and a confirmation.
providerin the path is the courier's slug. Only a courier linked from the courier list can be unlinked here; any other answers404 provider_not_linked.The body carries only the confirmation:
confirm_token, orconfirm: truefor keys not used by the assistant.When the unlinked courier was the default, the other courier that is turned on and was linked first becomes the default.
When there is none, no courier is the default and sends to delivery stop for the whole store. The answer then has
new_default: nullandsend_to_delivery_active: false.
Request
curl -X DELETE 'https://api.dzbuild.app/v1/shipping/providers/yalidine' \
-H "Authorization: Bearer $DZ_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: unlink-yalidine-2" \
-d '{"confirm_token": "cft_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"}'
Response 200
{
"data": {
"provider": "yalidine",
"unlinked": true,
"undoable": false,
"remaining_providers": 1,
"new_default": "noest",
"send_to_delivery_active": true,
"warning": "The store default is now \"noest\"; new send-to-delivery pushes go there."
}
}
GET /v1/shipping/coverage
Which wilayas, communes and stop desks a linked courier serves, from the courier's own data. A courier set in the store settings counts as linked here.
Auth: platform key with shipping:read.
Query parameters
Param | Type | Default | Notes |
| string | none | A linked courier's slug. Without it, the courier marked |
| int | 0 |
|
Request
curl 'https://api.dzbuild.app/v1/shipping/coverage?wilaya_id=16' \ -H "Authorization: Bearer $DZ_KEY"
Response 200
One commune and one desk are shown.
{
"data": {
"provider": "yalidine",
"is_send_default": true,
"knowledge_synced_at": "2026-10-05 03:12:44",
"wilayas": [
{ "wilaya_id": 16, "name": "Alger", "communes": 57, "communes_home": 57, "communes_desk": 12, "desks": 9 }
],
"wilaya_id": 16,
"desk_send_allowed": true,
"communes": [
{ "commune_id": 521, "name": "Alger Centre", "name_ar": "الجزائر الوسطى", "home": true, "desk": true }
],
"desks": [
{ "desk_id": "160101", "name": "Agence Alger Centre", "address": "Alger Centre", "phone": null, "commune_id": 521 }
]
}
}
Field | Meaning |
| When the platform last refreshed this courier's communes and desks. |
| Per wilaya: the number of |
| Whether |
|
|
| The courier's stop desks in the wilaya. The Custom themes & storefronts guide shows how to offer them at checkout. |
A store with no courier answers 422 no_courier_linked. A provider the store has not linked answers 404 provider_not_linked.
Undo rate and settings changes
POST /v1/shipping/rates, PATCH /v1/shipping/settings and a rate sync save the values they replace, so each can be undone with POST /v1/changes/{id}/undo.
A rate sync answers with its
change_id. The other two writes do not: find the change withGET /v1/changes?entity=shipping.ratesorGET /v1/changes?entity=shipping.settings, newest first. Listing changes needsstore:read.The undo needs
shipping:writeand anIdempotency-Key. The undo is itself a change,undo_change_id, which you can undo in turn, except the undo of a sync, which answers422 nothing_to_restore. See Changes and undo.Undoing a rate write deletes the rates of the wilayas that write created. Undoing a sync puts back the wilayas that had a rate before it; a wilaya the sync added keeps its new rate.
The undo writes back the saved values even when the rates or settings changed again since, in the dashboard or through the API.
A change undone a second time answers
409 already_undone.An installed app's token cannot list 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": "shipping.rates",
"undo_change_id": 510
}
}
Errors
HTTP | Code | Cause |
400 |
| The body is not a JSON object, |
400 |
|
|
400, 422 |
| 400: a |
400, 422 |
| 400: not a number. 422: negative or above 100000. |
400, 422 |
| 400: not a whole number. 422: outside 0 to 60. |
400 |
|
|
400 |
|
|
400, 422 |
| 400: not a number or |
400 |
|
|
422 |
|
|
400 |
|
|
400 |
| No |
400 |
|
|
400 |
|
|
400, 422 |
| 400: neither |
422 |
| The slug is not in |
422 |
| The |
422 |
| The courier refused the credentials. Nothing was saved. |
422 |
|
|
422 |
| Rate sync: the courier is not linked from the courier list, or is turned off. |
404 |
| Default, unlink or coverage: the store has not linked this courier. |
422 |
| Yalidine-family rate sync before the store's wilaya is set. |
422 |
| See the confirmation section above. |
422 |
| The prior rates could not be saved, so the write was refused rather than made impossible to undo. |
422 |
| Coverage for a store with no courier. |
422 |
| A write on a store that sells digital products. |
422 |
| The same |
403 |
| The key lacks the scope, for example "Missing scope: shipping:write", or a merchant key whose store is not on an active Enterprise plan. |
404 |
| Communes of a wilaya that does not exist. |
404 |
| The key's store no longer exists. |
429 |
| The same courier was synced less than 5 minutes ago. The message says how many minutes are left. |
429 |
| The courier budget or the store's request limit is spent. See Rate limits. |
503 |
| The sync could not be started and the rates did not change. Retry later. |
500 |
| Retry with the same |