DEV Community

Cover image for One SDK for every Algerian delivery carrier: Yalidine, ZR Express, DHD and Maystro in Node.js and PHP
developing-off
developing-off

Posted on

One SDK for every Algerian delivery carrier: Yalidine, ZR Express, DHD and Maystro in Node.js and PHP

🇫🇷 Version française : lire ici

If you have built an online store in Algeria, you know the drill: every delivery carrier has its own API, and none of them look alike.

  • Yalidine: X-API-ID / X-API-TOKEN headers, a mandatory origin wilaya, page_size pagination.
  • ZR Express: X-Api-Key + X-Tenant, a create endpoint that only returns a UUID, territory lookups through POST search endpoints.
  • DHD, Anderson, World Express (all on the Ecotrack platform): Bearer token, query-string parameters, orders that must be validated after creation.
  • Maystro: no tracking API at all — everything comes through webhooks.

And every one of them speaks its own status language: "En préparation", OrderReceived, 41…

So I built dz-delivery, an open-source (MIT) SDK that exposes one interface for all of these carriers, in TypeScript and PHP.

Install

npm install @dz-delivery/core      # Node.js ≥ 18, zero runtime dependencies
composer require dz-delivery/sdk   # PHP ≥ 8.2
Enter fullscreen mode Exit fullscreen mode

Same code, any carrier

import { createClient } from '@dz-delivery/core';

const shipping = createClient('yalidine', {
  apiId: process.env.YALIDINE_API_ID!,
  apiToken: process.env.YALIDINE_API_TOKEN!,
  fromWilayaId: 16, // origin wilaya (Algiers)
});
// or: createClient('zrexpress', { apiKey, tenant })
// or: createClient('ecotrack', { token, courier: 'dhd' })

const quote = await shipping.getFees({ toWilayaId: 31 });
// { deliveryFee, stopDeskFee, currency: 'DZD' }

const parcel = await shipping.createParcel({
  recipientName: 'Mohamed Amine',
  phone: '0550123456',
  address: 'Cité 221 lots, n°98',
  wilayaName: 'Oran',
  communeName: 'Bir El Djir',
  productName: 'T-shirt',
  price: 2500, // cash on delivery, in DZD
});

const { parcel: current, events } = await shipping.track(parcel.trackingNumber);
console.log(current.status); // 'pending' | 'in_transit' | 'delivered' | 'returned' | ...
Enter fullscreen mode Exit fullscreen mode

In PHP / Laravel it is the same thing with ClientFactory::create('yalidine', [...]).

What the SDK handles for you

  • Wilayas and communes: pass the official code (1–58), the carrier's own id, or a name — the SDK resolves it before calling the API.
  • Phone numbers: normalized to the format each carrier expects.
  • Unified statuses: pending, created, in_transit, delivered, returned, cancelled, unknown. The native response is still available in raw.
  • Typed errors: AUTH_FAILED, VALIDATION_ERROR, NOT_FOUND, RATE_LIMITED, NOT_SUPPORTED…
  • Stop desks: getStopDesks({ wilayaId }) for carrier offices and pickup points.
  • Account sync: listParcels() to pull parcel statuses (ZR Express).
  • ZR Express extras: PDF labels, bulk create/delete, signed webhook verification.

Carrier guides

Contributing

The code is on GitHub: https://github.com/developing-off/dz-delivery. Contributions are very welcome — especially new carriers (NoEST, MDM…) or anonymized real API responses to strengthen the test fixtures.

If you use it in production, let me know in the comments 🙏

Top comments (0)