# Livraison

Ces endpoints couvrent ce que le dashboard garde dans ses pages de livraison : le tarif de livraison à domicile et au bureau de chaque wilaya, les règles de livraison gratuite et les transporteurs liés à la boutique. Ils servent aussi les listes de wilayas et de communes dont un formulaire de commande a besoin, ainsi que les communes et les stop desks que dessert le transporteur de la boutique.

Les prix sont en DZD. `POST /v1/orders` calcule la livraison à partir de ces tarifs et ignore tout coût de livraison envoyé dans le corps : une vitrine personnalisée lit donc les tarifs pour afficher une estimation et laisse la commande calculer le montant. Voir [Thèmes & vitrines personnalisés](https://dzbuild.com/fr/fr/api-docs/guides/custom-storefronts.md).

## Avant de commencer[​](#avant-de-commencer "Lien direct vers Avant de commencer")

* La clé a besoin des portées de livraison. Les clés créées depuis le dashboard (**Paramètres → API**, `/dashboard/api`) ont les deux. Les portées sont figées à la création de la clé : une clé plus ancienne qui ne les a pas répond `403 forbidden`. Créez une nouvelle clé depuis le dashboard.
* Une boutique qui vend des produits numériques n'a pas de réglages de livraison : toute écriture de cette page y répond `422 shipping_not_available`.
* Chaque écriture demande un en-tête `Idempotency-Key`. Voir [Idempotence](https://dzbuild.com/fr/fr/api-docs/idempotency.md).
* Tester et lier un transporteur appellent ses serveurs pendant la requête, et une synchronisation des tarifs les appelle en arrière-plan. Ces trois appels et `POST /v1/orders/{id}/send-to-delivery` partagent un budget transporteur par boutique, en plus des [limites de taux](https://dzbuild.com/fr/fr/api-docs/rate-limits.md).
* Les écritures de tarifs et de réglages peuvent être annulées. La liaison, la déliaison et le changement de transporteur par défaut ne le peuvent pas. La section sur l'annulation, en fin de page, explique comment faire.
* Les lectures de livraison ne sont pas mises en cache par la passerelle : un `GET` envoyé juste après une écriture renvoie les nouvelles valeurs.

| Portée           | Description                                                                                                                                |
| ---------------- | ------------------------------------------------------------------------------------------------------------------------------------------ |
| `shipping:read`  | Lire les tarifs et réglages de livraison, les transporteurs liés, la couverture des transporteurs et les listes de wilayas et de communes. |
| `shipping:write` | Modifier les tarifs et réglages de livraison, et lier, tester, délier ou synchroniser les transporteurs.                                   |

## `GET /v1/wilayas`[​](#get-v1wilayas "Lien direct vers get-v1wilayas")

Les wilayas que la boutique dessert, selon son mode de wilayas : 1 à 58 en mode compatible transporteurs, 1 à 69 en mode 69 wilayas. Les noms sont en arabe, en français et en anglais. Toute la liste arrive en une réponse, sans pagination.

**Auth :** clé plateforme avec `shipping:read`.

### Requête[​](#requête "Lien direct vers Requête")

```
curl 'https://api.dzbuild.app/v1/wilayas' \

  -H "Authorization: Bearer $DZ_KEY"
```

### Réponse 200[​](#réponse-200 "Lien direct vers Réponse 200")

Deux des 58 wilayas sont montrées. `mode_note` est une phrase en anglais sur le 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`[​](#get-v1wilayasidcommunes "Lien direct vers get-v1wilayasidcommunes")

Les communes d'une wilaya, triées par nom français. Toute wilaya de 1 à 69 reçoit une réponse, quel que soit le mode de wilayas de la boutique. Toute la liste arrive en une réponse.

**Auth :** clé plateforme avec `shipping:read`.

C'est la liste de communes de la plateforme. Elle ne dit pas quelles communes un transporteur dessert : c'est le rôle de `GET /v1/shipping/coverage`.

### Requête[​](#requête-1 "Lien direct vers Requête")

```
curl 'https://api.dzbuild.app/v1/wilayas/16/communes' \

  -H "Authorization: Bearer $DZ_KEY"
```

### Réponse 200[​](#réponse-200-1 "Lien direct vers Réponse 200")

Deux des 57 communes de la wilaya 16 sont montrées.

```
{

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

    ]

  }

}
```

Un id qui n'est pas composé de chiffres répond `400 bad_request`. Une wilaya qui n'existe pas répond `404 not_found`.

## `GET /v1/shipping/rates`[​](#get-v1shippingrates "Lien direct vers get-v1shippingrates")

Le tarif de livraison de chaque wilaya qui a un tarif dans la boutique, indexé par id de wilaya. Une wilaya sans tarif est absente de `rates`.

**Auth :** clé plateforme avec `shipping:read`.

### Requête[​](#requête-2 "Lien direct vers Requête")

```
curl 'https://api.dzbuild.app/v1/shipping/rates' \

  -H "Authorization: Bearer $DZ_KEY"
```

### Réponse 200[​](#réponse-200-2 "Lien direct vers Réponse 200")

Une seule wilaya est montrée.

```
{

  "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

      }

    }

  }

}
```

| Champ                          | Signification                                                                                                                                          |
| ------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `wilaya_mode`                  | `58` ou `69`, la même valeur que dans `GET /v1/shipping/settings`.                                                                                     |
| `limits`                       | Le prix le plus haut et la valeur `days` la plus haute qu'accepte `POST /v1/shipping/rates`.                                                           |
| `count`                        | Nombre de wilayas dans `rates`.                                                                                                                        |
| `home_price`, `desk_price`     | Prix de la livraison à domicile et de la livraison au bureau, en DZD.                                                                                  |
| `home_enabled`, `desk_enabled` | Si la boutique propose ce type de livraison dans cette wilaya.                                                                                         |
| `days`                         | Délai de livraison en jours.                                                                                                                           |
| `synced_provider`, `synced_at` | Le transporteur dont la grille de tarifs a écrit ce tarif en dernier, et quand (`YYYY-MM-DD HH:MM:SS`). `null` si aucune synchronisation ne l'a écrit. |

## `POST /v1/shipping/rates`[​](#post-v1shippingrates "Lien direct vers post-v1shippingrates")

Crée ou modifie les tarifs des wilayas envoyées. Les autres wilayas ne sont pas touchées.

**Auth :** clé plateforme avec `shipping:write`. **Nécessite `Idempotency-Key`.**

### Corps[​](#corps "Lien direct vers Corps")

`rates` est un objet indexé par id de wilaya, écrit en chiffres seuls (`"16"`, pas `"016"`), avec 1 à 69 wilayas. Chaque valeur porte les champs à modifier, et chaque champ est optionnel.

| Champ          | Type   | Notes                                                                               |
| -------------- | ------ | ----------------------------------------------------------------------------------- |
| `home_price`   | number | En DZD, arrondi à deux décimales, de 0 à 100000. Une chaîne numérique est acceptée. |
| `home_enabled` | bool   | `true` ou `false`. `0`, `1`, `"0"` et `"1"` sont aussi acceptés.                    |
| `desk_price`   | number | Mêmes règles que `home_price`.                                                      |
| `desk_enabled` | bool   | Mêmes règles que `home_enabled`.                                                    |
| `days`         | int    | Un nombre entier de jours, de 0 à 60.                                               |

Un champ omis, ou envoyé à `null`, garde sa valeur enregistrée. Une wilaya qui n'avait pas de tarif part d'un prix de `0`, des deux types de livraison activés et de `3` jours.

### Requête[​](#requête-3 "Lien direct vers Requête")

```
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}}}'
```

### Réponse 200[​](#réponse-200-3 "Lien direct vers Réponse 200")

`rates` ne contient que les wilayas écrites, telles qu'enregistrées après l'écriture.

```
{

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

    }

  }

}
```

Les valeurs précédentes sont conservées : l'écriture peut donc être annulée. La réponse ne porte pas de `change_id` : voir la section sur l'annulation.

## `GET /v1/shipping/settings`[​](#get-v1shippingsettings "Lien direct vers get-v1shippingsettings")

Les règles de livraison gratuite et le mode de wilayas.

**Auth :** clé plateforme avec `shipping:read`.

### Requête[​](#requête-4 "Lien direct vers Requête")

```
curl 'https://api.dzbuild.app/v1/shipping/settings' \

  -H "Authorization: Bearer $DZ_KEY"
```

### Réponse 200[​](#réponse-200-4 "Lien direct vers Réponse 200")

La réponse porte aussi un objet `notes` avec deux phrases en anglais qui rappellent la règle du seuil et celle du mode de wilayas. L'exemple ne le montre pas.

```
{

  "data": {

    "free_shipping": false,

    "free_shipping_threshold": 8000,

    "free_shipping_threshold_active": true,

    "wilaya_mode": "58"

  }

}
```

| Champ                            | Signification                                                                                                                      |
| -------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------- |
| `free_shipping`                  | `true` quand la livraison est gratuite pour toutes les commandes.                                                                  |
| `free_shipping_threshold`        | Sous-total de commande en DZD à partir duquel la livraison est gratuite. `0` ou `null` veut dire pas de seuil.                     |
| `free_shipping_threshold_active` | `true` seulement quand le seuil est supérieur à `0`.                                                                               |
| `wilaya_mode`                    | `"58"` : les 58 wilayas avec lesquelles travaillent les transporteurs. `"69"` : les 69 wilayas, un mode réglé depuis le dashboard. |

## `PATCH /v1/shipping/settings`[​](#patch-v1shippingsettings "Lien direct vers patch-v1shippingsettings")

Modifie un ou plusieurs des trois réglages. Envoyez-en au moins un ; les autres champs sont ignorés.

**Auth :** clé plateforme avec `shipping:write`. **Nécessite `Idempotency-Key`.**

### Corps[​](#corps-1 "Lien direct vers Corps")

| Champ                     | Type           | Notes                                                                                                                                                                                                                                  |
| ------------------------- | -------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `free_shipping`           | bool           | `true` ou `false`. `0`, `1`, `"0"` et `"1"` sont aussi acceptés.                                                                                                                                                                       |
| `free_shipping_threshold` | number ou null | En DZD, arrondi à deux décimales, de 0 à 99999999.99. `0` ou `null` désactive le seuil.                                                                                                                                                |
| `wilaya_mode`             | string         | Seulement `"58"`, qui ramène une boutique en mode 69 wilayas à 58 wilayas. Le passage à 69 wilayas se fait depuis la page **Tarifs de livraison** du dashboard (`/dashboard/shipping`) et répond ici `422 wilaya_mode_69_unsupported`. |

### Requête[​](#requête-5 "Lien direct vers Requête")

```
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}'
```

### Réponse 200[​](#réponse-200-5 "Lien direct vers Réponse 200")

Les réglages après l'écriture, sans `notes`. Les valeurs précédentes sont conservées : le changement peut donc être annulé.

## `GET /v1/shipping/providers`[​](#get-v1shippingproviders "Lien direct vers get-v1shippingproviders")

Tous les transporteurs pris en charge par la plateforme, liés à la boutique ou non, avec ce que chacun demande quand vous le liez. Les valeurs des identifiants ne sont jamais renvoyées : `has_id` et `has_token` disent seulement si une valeur est enregistrée. Toute la liste arrive en une réponse.

**Auth :** clé plateforme avec `shipping:read`.

### Requête[​](#requête-6 "Lien direct vers Requête")

```
curl 'https://api.dzbuild.app/v1/shipping/providers' \

  -H "Authorization: Bearer $DZ_KEY"
```

### Réponse 200[​](#réponse-200-6 "Lien direct vers Réponse 200")

Un seul transporteur est montré. La réponse porte aussi une phrase `note`, omise ici.

```
{

  "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"

      }

    ]

  }

}
```

| Champ                                                | Signification                                                                                                                                                                                                                                        |
| ---------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `family`                                             | `yalidine`, `procolis`, `ecotrack` ou `standalone`.                                                                                                                                                                                                  |
| `credentials`                                        | Ce que `api_id` et `api_token` veulent dire pour ce transporteur, avec ses propres termes. `api_token` est absent pour un transporteur qui prend une seule valeur.                                                                                   |
| `extra_fields`                                       | Les autres champs que ce transporteur accepte quand vous le liez, indexés par nom. Un tableau vide quand il n'y en a pas.                                                                                                                            |
| `supports_rate_sync`                                 | Si `POST /v1/shipping/rates/sync` fonctionne avec ce transporteur.                                                                                                                                                                                   |
| `linked`                                             | Si la boutique a ce transporteur.                                                                                                                                                                                                                    |
| `source`                                             | `store_delivery_providers` pour un transporteur lié depuis la liste des transporteurs (cette API ou le dashboard), `store_row` pour un transporteur configuré à l'ancienne, directement dans les réglages de la boutique, `null` s'il n'est pas lié. |
| `is_enabled`                                         | Si la liaison est activée.                                                                                                                                                                                                                           |
| `is_default`                                         | Si c'est le transporteur par défaut de la boutique.                                                                                                                                                                                                  |
| `is_send_default`                                    | Le transporteur qu'utilise `POST /v1/orders/{id}/send-to-delivery` quand l'appel n'en nomme aucun.                                                                                                                                                   |
| `delivery_tier`, `synced_tier`, `economic_available` | Niveau de service de la famille Yalidine : celui choisi, celui utilisé par la dernière synchronisation des tarifs, et si le compte proposait le niveau économique lors de cette synchronisation.                                                     |
| `stock_account`, `auto_validate`, `custom_name`      | Les champs supplémentaires enregistrés pour ce transporteur, `null` s'ils ne sont pas définis.                                                                                                                                                       |
| `credentials_failed_at`                              | Heure ISO 8601, définie quand le transporteur a refusé à plusieurs reprises les identifiants enregistrés. Les envois vers ce transporteur sont refusés tant qu'elle est définie. Lier de nouveau le transporteur l'efface.                           |
| `linked_at`, `updated_at`                            | `YYYY-MM-DD HH:MM:SS`. `null` pour un transporteur configuré dans les réglages de la boutique.                                                                                                                                                       |

### Ce que contiennent `api_id` et `api_token`[​](#ce-que-contiennent-api_id-et-api_token "Lien direct vers ce-que-contiennent-api_id-et-api_token")

| `provider`                                                               | `api_id`             | `api_token`  |
| ------------------------------------------------------------------------ | -------------------- | ------------ |
| `yalidine`, `yalitec`, `guepex`, `easyandspeed`, `economiqua`, `wecan`   | API ID               | API Token    |
| `zrexpress`, `abexexpress`, `leopardexpress`, `colilog`, `flashdelivery` | Token                | Key          |
| `zrexpressnew`                                                           | API Key (secret key) | Tenant ID    |
| `noest`                                                                  | API Token            | User GUID    |
| `colivraison`                                                            | Public Key           | Bearer Token |
| `ecomdelivery`                                                           | API Key              | API Token    |
| `neardelivery`                                                           | ApiKey               | ApiSecret    |
| `maystro`                                                                | API Token            | aucun        |
| `zimou`                                                                  | Bearer Token         | aucun        |
| `elogistia`                                                              | API Key              | aucun        |
| `mdm`                                                                    | x-api-key            | aucun        |
| `customecotrack` et tous les transporteurs de la famille `ecotrack`      | Bearer Token         | aucun        |

Champs supplémentaires, tous optionnels sauf indication contraire :

* `delivery_tier`, famille Yalidine : `express`. `guepex` accepte aussi `economic`.
* `stock_account`, famille `ecotrack` : préparer les commandes depuis le stock du transporteur.
* `auto_validate`, `noest` : valider les commandes automatiquement chez le transporteur.
* `api_url` et `custom_name`, `customecotrack`, tous deux requis pour lier : l'adresse Ecotrack https du transporteur (un hôte qui se termine par `.ecotrack.dz`, ou `platform.dhd-dz.com` ou `app.conexlog-dz.com`) et le nom à afficher pour lui, jusqu'à 100 caractères.

## `POST /v1/shipping/providers/test`[​](#post-v1shippingproviderstest "Lien direct vers post-v1shippingproviderstest")

Envoie des identifiants au transporteur et indique s'il les a acceptés. Rien n'est enregistré. Un `api_id` ou un `api_token` vide ou absent reprend la valeur enregistrée pour ce transporteur : vous pouvez donc retester un transporteur lié sans détenir ses identifiants.

**Auth :** clé plateforme avec `shipping:write`. **Nécessite `Idempotency-Key`.** Compte dans le budget transporteur.

### Corps[​](#corps-2 "Lien direct vers Corps")

| Champ           | Type   | Requis                     | Notes                                                                                                                                   |
| --------------- | ------ | -------------------------- | --------------------------------------------------------------------------------------------------------------------------------------- |
| `provider`      | string | oui                        | Un slug de `GET /v1/shipping/providers`.                                                                                                |
| `api_id`        | string | sauf si enregistré         | Premier identifiant.                                                                                                                    |
| `api_token`     | string | sauf si enregistré         | Second identifiant, pour les transporteurs qui en prennent deux.                                                                        |
| `api_url`       | string | `customecotrack` seulement | L'adresse Ecotrack du transporteur. Quand les identifiants enregistrés sont réutilisés, elle doit correspondre à l'adresse enregistrée. |
| `delivery_tier` | string | non                        | `express` ou `economic`. `economic` n'est accepté que pour `guepex`.                                                                    |

### Requête[​](#requête-7 "Lien direct vers Requête")

```
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"}'
```

### Réponse 200[​](#réponse-200-7 "Lien direct vers Réponse 200")

Un transporteur qui refuse les identifiants répond aussi `200`, avec `ok: false` et le `message` de la vérification chez le transporteur. `resolved_provider` n'est défini que pour `zrexpressnew` : la plateforme ZR Express qui a accepté la paire.

```
{

  "data": {

    "provider": "yalidine",

    "ok": true,

    "message": "تم الاتصال بنجاح",

    "resolved_provider": null,

    "saved": false

  }

}
```

## `POST /v1/shipping/providers`[​](#post-v1shippingproviders "Lien direct vers post-v1shippingproviders")

Lie un transporteur, ou réenregistre un transporteur lié. La plateforme teste d'abord les identifiants auprès du transporteur et n'enregistre rien s'il les refuse.

**Auth :** clé plateforme avec `shipping:write`. **Nécessite `Idempotency-Key`.** Compte dans le budget transporteur.

### Corps[​](#corps-3 "Lien direct vers Corps")

Les champs de l'appel de test, plus :

| Champ           | Type   | Défaut             | Notes                                                                                                       |
| --------------- | ------ | ------------------ | ----------------------------------------------------------------------------------------------------------- |
| `enabled`       | bool   | `true`             | Activer ou désactiver la liaison.                                                                           |
| `set_default`   | bool   | `false`            | Faire de ce transporteur celui par défaut de la boutique.                                                   |
| `custom_name`   | string | aucun              | `customecotrack` seulement, requis là. Les balises HTML sont retirées et le nom est coupé à 100 caractères. |
| `stock_account` | bool   | valeur enregistrée | Famille `ecotrack`.                                                                                         |
| `auto_validate` | bool   | valeur enregistrée | `noest`.                                                                                                    |

* Un `api_id` ou un `api_token` vide garde la valeur enregistrée : un transporteur lié peut être réenregistré sans renvoyer ses identifiants.
* Le premier transporteur que lie une boutique devient son transporteur par défaut. Dans la réponse, `is_default` vaut `true` seulement quand cet appel a fait du transporteur celui par défaut : un transporteur par défaut réenregistré sans `set_default` le reste alors que la réponse dit `false`. `GET /v1/shipping/providers` montre l'état réel.
* `zrexpress` et `zrexpressnew` sont un seul transporteur : lier l'un remplace l'autre. Une paire `zrexpressnew` acceptée par l'ancienne plateforme ZR Express est enregistrée comme `zrexpress`, et `provider` dans la réponse l'indique.

### Requête[​](#requête-8 "Lien direct vers Requête")

```
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}'
```

### Réponse 200[​](#réponse-200-8 "Lien direct vers Réponse 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."

  }

}
```

Un identifiant refusé répond `422 credentials_rejected` avec le message du transporteur, et rien n'est enregistré.

## `POST /v1/shipping/providers/default`[​](#post-v1shippingprovidersdefault "Lien direct vers post-v1shippingprovidersdefault")

Fait d'un transporteur lié celui par défaut de la boutique. Les nouveaux envois en livraison partent vers lui.

**Auth :** clé plateforme avec `shipping:write`. **Nécessite `Idempotency-Key`.**

`provider` dans le corps nomme le transporteur. Seul un transporteur lié depuis la liste des transporteurs peut devenir celui par défaut ; un transporteur configuré dans les réglages de la boutique répond `404 provider_not_linked`. Quand le transporteur choisi est désactivé, `warning` indique que les envois restent coupés jusqu'à ce qu'il soit réactivé.

### Requête[​](#requête-9 "Lien direct vers Requête")

```
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"}'
```

### Réponse 200[​](#réponse-200-9 "Lien direct vers Réponse 200")

```
{

  "data": {

    "provider": "noest",

    "is_default": true,

    "previous_default": "yalidine",

    "undoable": false,

    "warning": "New send-to-delivery pushes now go to \"noest\". "

  }

}
```

## Confirmation avant une synchronisation des tarifs ou une déliaison[​](#confirmation-avant-une-synchronisation-des-tarifs-ou-une-déliaison "Lien direct vers Confirmation avant une synchronisation des tarifs ou une déliaison")

Une synchronisation écrase les prix du marchand et une déliaison retire des identifiants enregistrés : ces deux appels demandent donc une confirmation avant d'agir.

1. Appelez sans confirmation. La réponse est `422 confirmation_required`, et l'objet `error` ajoute `confirm_token` (usage unique), `confirm_token_expires_in` (`600` secondes), `action` et `will_change`, le résumé à montrer au marchand.
2. Une fois que le marchand approuve, refaites l'appel avec `confirm_token` dans le corps et une **nouvelle** `Idempotency-Key`. La première clé est liée au corps sans le jeton : la réutiliser répond `422 idempotency_key_reuse`.

Un jeton fonctionne une seule fois, seulement pour la clé qui l'a reçu, et seulement tant que ce qu'il décrit n'a pas changé : la table des tarifs pour une synchronisation, et pour une déliaison le nombre de transporteurs liés et le fait que celui-ci soit ou non celui par défaut. Un jeton déjà utilisé, expiré ou qui ne correspond plus répond `422 confirmation_stale` avec un nouveau jeton et un nouveau résumé.

Une clé qui n'est pas utilisée par l'assistant intégré au dashboard peut envoyer `"confirm": true` au lieu d'un jeton et sauter l'étape 1. Les clés utilisées par l'assistant doivent envoyer le jeton.

Voici la première réponse d'une synchronisation.

```
{

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

    }

  }

}
```

Pour une déliaison, `will_change` contient `action`, `was_store_default`, `remaining_providers`, `consequence`, `reversible` (`false`) et `note`.

## `POST /v1/shipping/rates/sync`[​](#post-v1shippingratessync "Lien direct vers post-v1shippingratessync")

Remplace les prix de la boutique par la grille de tarifs du transporteur, pour chaque wilaya de 1 à 58 que le transporteur tarifie. La synchronisation tourne en arrière-plan. Avant toute mise en file, toute la table des tarifs est conservée, et le `change_id` de la réponse annule la synchronisation.

**Auth :** clé plateforme avec `shipping:write`. **Nécessite `Idempotency-Key`** et une confirmation. Compte dans le budget transporteur.

### Corps[​](#corps-4 "Lien direct vers Corps")

| Champ           | Type   | Requis         | Notes                                                                                                                   |
| --------------- | ------ | -------------- | ----------------------------------------------------------------------------------------------------------------------- |
| `provider`      | string | oui            | Un transporteur lié depuis la liste des transporteurs et activé. `mdm` et `neardelivery` n'ont pas de grille de tarifs. |
| `confirm_token` | string | voir plus haut | Tiré de la réponse `confirmation_required`.                                                                             |
| `confirm`       | bool   | voir plus haut | `true`, pour les clés non utilisées par l'assistant.                                                                    |

### Ce que change la synchronisation[​](#ce-que-change-la-synchronisation "Lien direct vers Ce que change la synchronisation")

* Elle écrit `home_price` et `desk_price` et renseigne `synced_provider` et `synced_at`. Les interrupteurs et `days` d'une wilaya qui avait déjà un tarif restent tels quels. Une wilaya qui n'avait pas de tarif reçoit `3` jours.
* Les transporteurs de la famille Yalidine ont besoin de la wilaya de la boutique, réglée avec `wilaya_id` dans `PATCH /v1/store` (voir [Boutique](https://dzbuild.com/fr/fr/api-docs/resources/store.md)). Sans elle, l'appel répond `422 store_wilaya_required`.
* Suivez le résultat avec `GET /v1/shipping/rates` : les tarifs écrits par la synchronisation nomment le transporteur dans `synced_provider` et portent un nouveau `synced_at`. Quand le transporteur n'envoie aucun prix, les tarifs restent tels quels.
* Tant qu'une synchronisation du même transporteur tourne, l'appel répond `202` avec `status: already_running`, le `sync_id` de cette synchronisation et `change_id: null`, sans demander de confirmation.
* Après une synchronisation réussie, le même transporteur peut être resynchronisé 5 minutes plus tard. Un appel plus tôt répond `429 sync_cooldown`.

### Requête[​](#requête-10 "Lien direct vers Requête")

```
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"}'
```

### Réponse 202[​](#réponse-202 "Lien direct vers Réponse 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}`[​](#delete-v1shippingprovidersprovider "Lien direct vers delete-v1shippingprovidersprovider")

Délie un transporteur et retire ses identifiants de la boutique. C'est irréversible : pour envoyer de nouveau avec ce transporteur, liez-le de nouveau. Les colis déjà chez le transporteur restent suivis.

**Auth :** clé plateforme avec `shipping:write`. **Nécessite `Idempotency-Key`** et une confirmation.

* `provider` dans le chemin est le slug du transporteur. Seul un transporteur lié depuis la liste des transporteurs peut être délié ici ; tout autre répond `404 provider_not_linked`.
* Le corps ne porte que la confirmation : `confirm_token`, ou `confirm: true` pour les clés non utilisées par l'assistant.
* Quand le transporteur délié était celui par défaut, l'autre transporteur activé lié en premier devient celui par défaut.
* S'il n'y en a aucun, aucun transporteur n'est par défaut et les envois en livraison s'arrêtent pour toute la boutique. La réponse porte alors `new_default: null` et `send_to_delivery_active: false`.

### Requête[​](#requête-11 "Lien direct vers Requête")

```
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"}'
```

### Réponse 200[​](#réponse-200-10 "Lien direct vers Réponse 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`[​](#get-v1shippingcoverage "Lien direct vers get-v1shippingcoverage")

Les wilayas, communes et stop desks que dessert un transporteur lié, d'après les données du transporteur lui-même. Un transporteur configuré dans les réglages de la boutique compte ici comme lié.

**Auth :** clé plateforme avec `shipping:read`.

### Paramètres de requête[​](#paramètres-de-requête "Lien direct vers Paramètres de requête")

| Param       | Type   | Défaut | Notes                                                                                                                                                                            |
| ----------- | ------ | ------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `provider`  | string | aucun  | Le slug d'un transporteur lié. Sans lui, le transporteur marqué `is_send_default`, sinon le premier lié.                                                                         |
| `wilaya_id` | int    | 0      | `0` donne un décompte par wilaya. `1` à `69` ajoute les communes de cette wilaya, ses stop desks et `desk_send_allowed`. Une valeur hors de `0` à `69` répond `400 bad_request`. |

### Requête[​](#requête-12 "Lien direct vers Requête")

```
curl 'https://api.dzbuild.app/v1/shipping/coverage?wilaya_id=16' \

  -H "Authorization: Bearer $DZ_KEY"
```

### Réponse 200[​](#réponse-200-11 "Lien direct vers Réponse 200")

Une commune et un stop desk sont montrés.

```
{

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

    ]

  }

}
```

| Champ                 | Signification                                                                                                                                                                                                                                                      |
| --------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `knowledge_synced_at` | La dernière fois que la plateforme a rafraîchi les communes et les stop desks de ce transporteur. `null` si elle ne l'a jamais fait.                                                                                                                               |
| `wilayas`             | Par wilaya : le nombre de `communes`, combien reçoivent la livraison à domicile (`communes_home`) et au bureau (`communes_desk`), et le nombre de `desks`. Avec `wilaya_id`, seulement cette wilaya.                                                               |
| `desk_send_allowed`   | Si `POST /v1/orders/{id}/send-to-delivery` accepte une commande au bureau vers cette wilaya avec ce transporteur. C'est la même vérification.                                                                                                                      |
| `communes`            | `commune_id` est l'id de `GET /v1/wilayas/{id}/communes`, ou `null` quand la commune du transporteur n'a pas de correspondance, et `name` est alors le nom donné par le transporteur. `home` et `desk` disent quels types de livraison le transporteur propose là. |
| `desks`               | Les stop desks du transporteur dans la wilaya. Le guide [Thèmes & vitrines personnalisés](https://dzbuild.com/fr/fr/api-docs/guides/custom-storefronts.md) montre comment les proposer au checkout.                                                                |

Une boutique sans transporteur répond `422 no_courier_linked`. Un `provider` que la boutique n'a pas lié répond `404 provider_not_linked`.

## Annuler les changements de tarifs et de réglages[​](#annuler-les-changements-de-tarifs-et-de-réglages "Lien direct vers Annuler les changements de tarifs et de réglages")

`POST /v1/shipping/rates`, `PATCH /v1/shipping/settings` et une synchronisation des tarifs conservent les valeurs qu'ils remplacent : chacun peut donc être annulé avec `POST /v1/changes/{id}/undo`.

* Une synchronisation répond avec son `change_id`. Les deux autres écritures non : trouvez le changement avec `GET /v1/changes?entity=shipping.rates` ou `GET /v1/changes?entity=shipping.settings`, du plus récent au plus ancien. Lister les changements demande `store:read`.
* L'annulation demande `shipping:write` et une `Idempotency-Key`. L'annulation est elle-même un changement, `undo_change_id`, que vous pouvez annuler à son tour, sauf l'annulation d'une synchronisation, qui répond `422 nothing_to_restore`. Voir [Changements et annulation](https://dzbuild.com/fr/fr/api-docs/resources/changes.md).
* Annuler une écriture de tarifs supprime les tarifs des wilayas que cette écriture a créés. Annuler une synchronisation remet les wilayas qui avaient un tarif avant elle ; une wilaya ajoutée par la synchronisation garde son nouveau tarif.
* L'annulation réécrit les valeurs conservées même si les tarifs ou les réglages ont changé depuis, dans le dashboard ou par l'API.
* Un changement annulé une seconde fois répond `409 already_undone`.
* Le jeton d'une application installée ne peut ni lister ni annuler les changements : les deux répondent `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

  }

}
```

## Erreurs[​](#erreurs "Lien direct vers Erreurs")

| HTTP     | Code                                          | Cause                                                                                                                                                                                     |
| -------- | --------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| 400      | `bad_request`                                 | Le corps n'est pas un objet JSON, `rates` manque ou n'est pas un objet, un id dans le chemin est mal formé, `wilaya_id` est hors de 0 à 69, ou `Idempotency-Key` manque ou est mal formé. |
| 400      | `invalid_rates`                               | `rates` est vide ou a plus de 69 wilayas, un tarif n'est pas un objet, ou un interrupteur n'est pas un booléen.                                                                           |
| 400, 422 | `invalid_wilaya`                              | 400 : une clé de `rates` n'est pas en chiffres seuls. 422 : aucune wilaya n'a cet id.                                                                                                     |
| 400, 422 | `invalid_price`                               | 400 : pas un nombre. 422 : négatif ou supérieur à 100000.                                                                                                                                 |
| 400, 422 | `invalid_days`                                | 400 : pas un nombre entier. 422 : hors de 0 à 60.                                                                                                                                         |
| 400      | `nothing_to_update`                           | `PATCH /v1/shipping/settings` sans aucun de ses trois champs.                                                                                                                             |
| 400      | `invalid_free_shipping`                       | `free_shipping` n'est pas un booléen.                                                                                                                                                     |
| 400, 422 | `invalid_threshold`                           | 400 : ni un nombre ni `null`. 422 : négatif ou supérieur à 99999999.99.                                                                                                                   |
| 400      | `invalid_wilaya_mode`                         | `wilaya_mode` n'est ni `"58"` ni `"69"`.                                                                                                                                                  |
| 422      | `wilaya_mode_69_unsupported`                  | `wilaya_mode` vaut `"69"`, qui se règle depuis le dashboard.                                                                                                                              |
| 400      | `provider_required`                           | `provider` manque.                                                                                                                                                                        |
| 400      | `credentials_required`                        | Aucun `api_id` envoyé ni enregistré, ou aucun `api_token` pour un transporteur qui prend deux valeurs.                                                                                    |
| 400      | `invalid_credentials_format`                  | Des valeurs `zrexpressnew` qui contiennent des accolades JSON, des espaces ou des retours à la ligne, qui commencent par `http`, ou qui dépassent 128 caractères.                         |
| 400      | `api_url_required`, `custom_name_required`    | `customecotrack` sans son adresse, ou une liaison sans son nom.                                                                                                                           |
| 400, 422 | `invalid_delivery_tier`                       | 400 : ni `express` ni `economic`. 422 : `economic` pour un autre transporteur que `guepex`.                                                                                               |
| 422      | `unsupported_provider`                        | Le slug n'est pas dans `GET /v1/shipping/providers`.                                                                                                                                      |
| 422      | `invalid_api_url`, `api_url_mismatch`         | L'adresse `customecotrack` n'est pas une adresse Ecotrack https, ou diffère de celle enregistrée alors que les identifiants enregistrés sont réutilisés.                                  |
| 422      | `credentials_rejected`                        | Le transporteur a refusé les identifiants. Rien n'a été enregistré.                                                                                                                       |
| 422      | `rate_sync_unsupported`                       | `mdm` et `neardelivery` n'ont pas de grille de tarifs.                                                                                                                                    |
| 422      | `provider_not_linked`                         | Synchronisation : le transporteur n'est pas lié depuis la liste des transporteurs, ou est désactivé.                                                                                      |
| 404      | `provider_not_linked`                         | Défaut, déliaison ou couverture : la boutique n'a pas lié ce transporteur.                                                                                                                |
| 422      | `store_wilaya_required`                       | Synchronisation d'un transporteur de la famille Yalidine avant que la wilaya de la boutique soit réglée.                                                                                  |
| 422      | `confirmation_required`, `confirmation_stale` | Voir la section sur la confirmation plus haut.                                                                                                                                            |
| 422      | `snapshot_too_large`, `snapshot_failed`       | Les tarifs précédents n'ont pas pu être conservés : l'écriture a été refusée plutôt que de devenir impossible à annuler.                                                                  |
| 422      | `no_courier_linked`                           | Couverture pour une boutique sans transporteur.                                                                                                                                           |
| 422      | `shipping_not_available`                      | Une écriture sur une boutique qui vend des produits numériques.                                                                                                                           |
| 422      | `idempotency_key_reuse`                       | La même `Idempotency-Key` avec un corps différent.                                                                                                                                        |
| 403      | `forbidden`                                   | La clé n'a pas la portée, par exemple « Missing scope: shipping<!-- -->:write<!-- --> », ou clé du marchand dont la boutique n'a pas de plan Enterprise actif.                            |
| 404      | `not_found`                                   | Communes d'une wilaya qui n'existe pas.                                                                                                                                                   |
| 404      | `store_not_found`                             | La boutique de la clé n'existe plus.                                                                                                                                                      |
| 429      | `sync_cooldown`                               | Le même transporteur a été synchronisé il y a moins de 5 minutes. Le message indique combien de minutes il reste.                                                                         |
| 429      | `rate_limited`, `too_many_concurrent`         | Le budget transporteur ou la limite de requêtes de la boutique est épuisé. Voir [Limites de taux](https://dzbuild.com/fr/fr/api-docs/rate-limits.md).                                     |
| 503      | `sync_queue_failed`                           | La synchronisation n'a pas pu démarrer et les tarifs n'ont pas changé. Réessayez plus tard.                                                                                               |
| 500      | `server_error`                                | Réessayez avec la même `Idempotency-Key`.                                                                                                                                                 |
