How it works
Every request carries three things: which courier to use,
your own credentials for that courier, and the order itself.
The parcel is created on your courier account; credentials are used for that one
call and never stored. Switching courier is a one-line change — the request shape and the
response shape stay the same.
Endpoints
Base URL https://freeship.dzbuild.com · all bodies JSON.
Create a parcel
The hero example above is complete and real — run it with your Yalidine credentials and a parcel lands on your account, label included. The fields that matter:
| Field | Notes | |
|---|---|---|
courier | REQUIRED | yalidine · zrexpress · maystro · noest · ecotrack |
credentials | REQUIRED | Your courier account's API credentials — see couriers |
order.recipient.fullName | REQUIRED | 2–120 chars |
order.recipient.phone | REQUIRED | Valid Algerian mobile (05/06/07…) — else 422 invalid_phone |
order.recipient.wilayaCode | REQUIRED | 1–58, see GET /v1/wilayas |
order.recipient.communeName | REQUIRED | The courier's exact French spelling. Wrong commune = the #1 create error |
order.deliveryType | REQUIRED | home or stopdesk (+ stopDeskId for Yalidine/NOEST desks) |
order.productList | REQUIRED | Printed on the label; long lists truncated safely per courier |
order.codAmount | REQUIRED | Cash to collect, integer DZD. 0 = prepaid |
options.fromWilaya | YALIDINE | Your origin wilaya — required to create and to quote |
order.reference | OPTIONAL | Your order id; omitted → generated (FS-…) |
All optional fields
| Field | Notes |
|---|---|
order.recipient.phoneAlt | Second phone — couriers call it after a failed first contact |
order.recipient.addressLine | Street address; optional for stop-desk |
order.weightKg | Parcel weight |
order.declaredValue | Enables insurance where supported (Yalidine) |
order.freeShipping | Customer not charged shipping — otherwise Yalidine adds its fee to the COD |
order.isExchange | Exchange / swap parcel |
order.hasOpenPackage | Customer may open before paying (NOEST) |
order.notes | Remark for the driver |
options.baseUrl | Ecotrack tenant URL, or a Yalidine-family network host |
options.timeoutMs | Upstream courier timeout |
Track a parcel
On demand — call it on page load, from a cron, wherever. rawStatus
always carries the courier's original wording.
$ curl -X POST https://freeship.dzbuild.com/v1/track \ -H 'Content-Type: application/json' -d '{ "courier": "yalidine", "credentials": { "apiId": "…", "apiToken": "…" }, "trackingNumber": "yal-ABC123" }' → 200 { "status": "out_for_delivery", "events": [ { "status": "picked_up", "rawStatus": "Ramassé", "timestamp": "2026-07-08T09:00:00Z" }, { "status": "out_for_delivery", "rawStatus": "Sorti en livraison", "timestamp": "2026-07-09T08:30:00Z" } ] }
One status vocabulary
Every courier's dialect — French sentences, Arabic labels, bare numbers — maps onto one closed set. Build your UI against it once.
delivered, returned,
cancelled, lost are terminal. unknown means the courier
emitted new wording — show rawStatus and keep polling. French + Arabic UI labels
for all fourteen are in the
status guide.
Quote a fee
$ curl -X POST https://freeship.dzbuild.com/v1/rates \ -H 'Content-Type: application/json' -d '{ "courier": "yalidine", "credentials": { "apiId": "…", "apiToken": "…" }, "query": { "fromWilaya": 16, "toWilaya": 31, "deliveryType": "home" } }' → 200 { "deliveryFee": 600, "returnFee": 250, "total": 600, "currency": "DZD" }
Note the returnFee — your COD unit
economics depend on it as much as on the delivery fee. query.toCommune refines
per-commune pricing; query.tier accepts express (default) or
economic. Deep-south wilayas (isDeepSouth in
/v1/wilayas) carry surcharges everywhere — quote, don't hardcode.
Couriers
Credentials come from your own account on each courier's dashboard.
GET /v1/couriers returns this list live, with capabilities.
| Courier | courier | credentials | Notes |
|---|---|---|---|
| Yalidine | yalidine | apiId, apiToken | options.fromWilaya required. Labels at creation. Densest stop-desk network |
| Yalitec · Guepex · Easy & Speed | yalidine | apiId, apiToken | Same platform — add options.baseUrl for that network |
| ZR Express (Procolis) | zrexpress | token, key | Exchanges supported. Labels from the dashboard |
| Maystro | maystro | apiKey | Commune IDs resolved for you. Same name+phone twice a day is rejected by Maystro |
| NOEST | noest | apiToken, guid | Order validation handled — created means pickup-ready. Desk codes like 16A |
| Ecotrack (DHD, Conexlog, MSM Go, +30) | ecotrack | token | options.baseUrl = your tenant, e.g. https://platform.dhd-dz.com |
Choosing between them, per-courier behavior, COD mechanics: the dzship field guides cover what the comparison tables don't.
Errors
One envelope everywhere:
{ "error": { "code", "message", "fields?" } }
| HTTP | Code | What to do |
|---|---|---|
| 400 | VALIDATION_ERROR | Fix the fields listed in error.fields |
| 422 | invalid_phone | Not a valid Algerian mobile — correct the number |
| 422 / 502 | NOT_SUPPORTED / COURIER_ERROR | The courier rejected it — read message (bad credentials, unknown commune, missing stop desk…) |
| 429 | rate_limited | Wait Retry-After seconds, then retry |
| 503 | overloaded | Momentary capacity protection — retry shortly |
Fair use
Hard server-side limits keep the API fast and free for everyone.
| Limit | Value |
|---|---|
| Orders / hour / IP | 30 |
| Orders / day / IP | 100 |
| Orders / day / recipient phone | 10 |
| Tracking calls / minute / IP | 30 |
| Tracking calls / day / IP | 2 000 |
| Rate quotes / minute / IP | 20 |
Design around them: track on page-view rather than tight loops, and cache
/v1/wilayas and /v1/couriers — they rarely change.
dzship is the shipping. DZBuild is the rest.
Arabic/French storefronts, landing pages, COD order management with confirmation workflows, 80+ couriers pre-wired, stock, analytics — and a dashboard your client can actually use.