EcoTrack Integration
EcoTrack is not a courier. It is the white-label platform a large slice of
Algeria's regional couriers run on: DHD, Conexlog, MSM Go, World Express,
Packers and ~80 others each expose the SAME API on their own *.ecotrack.dz
domain. One adapter, 82 companies. The companies differ (fleet, pricing,
delivery rate); the API does not.
Resources in this folder
| File |
What it holds |
about.md |
dzship's EcoTrack guide — the 82-courier key table + per-tenant quirks |
postman_collection.json |
The OFFICIAL API docs (Postman export) — the source of truth |
API-REFERENCE.md |
Every endpoint distilled from the Postman collection |
CODEBASE-MAP.md |
Where our integration lives, data flow, DB shape |
CONFORMANCE.md |
Verified audit: implemented ✓ / missing / wrong, verification checklist |
PLAN.md |
The slice-by-slice rollout plan for all 82 couriers — follow it top to bottom and keep its status table current |
Read API-REFERENCE.md before writing any adapter code. Read CONFORMANCE.md
before assuming current behavior is correct. For the 82-courier rollout work,
PLAN.md is the operational document: pick the first slice marked NOT STARTED,
follow its checklist, verify green, mark it DONE.
The mental model
delivery_companies row (code: "dhd_ecotrack", apiEndpoint: "https://dhd.ecotrack.dz", apiToken: "…")
│
▼
isEcotrackCompany(code) → EcotrackProvider(apiToken, baseUrl)
│
▼ one adapter, any *.ecotrack.dz tenant
POST /api/v1/create/order (query params, Bearer auth)
- Company code MUST be
ecotrack or end in _ecotrack — that suffix is what
routes the company to this adapter (registry.ts:32).
apiEndpoint holds the tenant base URL. There is no central EcoTrack host.
apiUserGuid is unused (NOEST-only field).
autoValidate defaults to false for the EcoTrack family — parcels wait
as Dispatched until someone calls manual validation.
Workflow A — Connect a new EcoTrack courier
- Find the courier key in
about.md's 82-courier table (e.g. dhd, conexlog, msmgo).
- Create the delivery company with:
code = {key}_ecotrack (or plain ecotrack for a generic tenant)
apiEndpoint = https://{key}.ecotrack.dz (the pattern; verify with the
courier if unsure — generic fallback requires a *.ecotrack.dz host)
apiToken = token from that courier's dashboard (write-only; never logged)
name/nameAr per the courier's branding
- Decide
autoValidate deliberately (default false for EcoTrack family —
manual validation keeps control of when the parcel enters courier flow).
- Run stop-desk sync (admin action →
getStopDesks() → get/communes filtered
to has_stop_desk === 1, code_postal becomes the Station Code).
- Verify: token check, one test dispatch, label proxy, tracking pull.
Workflow B — Audit / verify the integration
- Open
CONFORMANCE.md — read the "Verified correct" and "Discrepancies" lists.
- Diff
adapter.ts + types.ts against API-REFERENCE.md, endpoint by endpoint.
- Check response-shape handling for every endpoint you touch — EcoTrack shapes
are NOT uniform (see Hard Rules #3).
- If you changed TypeScript:
cd cod-server && npm run typecheck.
- If you changed behavior:
cd cod-server && npm test — and note the EcoTrack
adapter has NO test file (yalidine/noest/zr_express all have
adapter.test.ts); when you touch the adapter, add/extend one using the same
fetch-mock pattern.
- Live API verification only when the user explicitly provides a sandbox tenant
and token. Never commit credentials.
.dev.vars only.
Workflow C — Extend the adapter
- Copy the exact param names / response shape from
API-REFERENCE.md — do not
guess, do not copy from another provider. EcoTrack uses French param names
and three different response styles.
- Single-order endpoints take query params, no JSON body. Only
create/orders (bulk) and valid/returns take a JSON body.
- Keep every response-shape guard defensive (
Array.isArray, ?? []) —
tenants drift.
- Register nothing in the registry unless a new company family appears —
*_ecotrack codes already route here.
- Add tests. Run typecheck + tests.
Hard rules (traps that bite)
- Auth is Bearer everywhere — EXCEPT
GET /api/v1/get/orders/status, which
authenticates via an api_token query param. If you send only the
Bearer header there, you get an auth error.
- Rate limit: 50 requests/minute → HTTP 429
{"message": "Too Many Attempts."}.
Auto-validate flows make 2 calls per order (create + valid) — a bulk dispatch
of 25+ orders can hit the ceiling. Surface 429 distinctly; pace or back off.
- Response shapes are not uniform. Memorize:
get/maj → plain JSON array
get/tracking/info → object with activity array
get/communes → object keyed by index (never assume array)
get/wilayas, get/orders data → plain array / paginated object
get/desks → { my_desk: {...}, other_desks: [...] }
- bulk
create/orders results → keyed by reference when the order had
one, else by index string
- Single create errors → HTTP 200 with
{success: false, error: 1000x} OR
HTTP 422 Laravel errors bag. Handle BOTH.
montant is the COD amount INCLUDING delivery fees (official param doc).
CodFlow currently sends order.price (product subtotal) at
dispatch.ts createShipment call — see CONFORMANCE.md before shipping a fix;
confirm merchant intent (delivery fee charged to customer ⇒ must be included).
type param: 1=Livraison, 2=Échange, 3=PICKUP, 4=Recouvrement. Our
adapter always sends 1. capabilities.ts claiming canExchange: false
contradicts the official docs — all four types are allowed.
- Update semantics: officially all params optional, but Packers (one
tenant) rejects calls missing
type, wilaya, commune, adresse, montant, tel.
The caller must pre-fill ALL fields from the order, then apply overrides —
the route handler already does this; keep it that way.
- Update after validation is a silent no-op: EcoTrack answers
success: true but ignores the change (Packers-confirmed). We guard
client-side (status must be dispatched). Never trust a post-validation
update response.
- Delete only works pre-validation (error 10001 after). Post-validation,
the only carrier-side actions are
ask/for/order/return (courier may
IGNORE it) and valid/returns (confirming received returns).
- Labels are raw PDF bytes behind Bearer auth — the URL is never public.
Always serve through
proxyShipmentLabel (server-side fetch), never expose
the token to the browser.
- No webhooks. Tracking is pull-only:
get/tracking/info (single) and
get/trackings/info (bulk, ≤100). The /orders/:id/tracking endpoint is
the only freshness source for EcoTrack orders.
- Status wording drifts per tenant. Never treat French display labels as
stable identifiers. The stable vocabulary is the
activity enum keys and
the status enum keys (both listed in API-REFERENCE.md). An unknown label
must surface as-is, never be guessed into a status.
- Tracking queries answer list-style — a lazy "take the first row" client
can attach the wrong parcel's status. Always query with the exact tracking
number and match exactly.
- Validation limits: phone 9–10 digits numeric; wilaya code 1–58 integer;
every string field max 255 chars.
commune is a NAME (string), matched
against the tenant's enabled commune list — a wrong/misspelled commune name
is a 422, and a disabled wilaya returns error 10002.
- Bulk endpoint reliability is per-tenant: Packers'
create/orders
returned HTTP 500 (server-side bug, documented in dispatch.ts). Verify on
the specific tenant before relying on bulk dispatch; fall back to
sequential single creates.
- The
_ecotrack suffix is load-bearing: adapter selection, the
autoValidate default, and the update guard all key off
isEcotrackCompany(code). A company code without the suffix silently loses
all three.
What "done right" looks like
- Every param name matches
API-REFERENCE.md exactly (French, snake_case).
- Every response parse handles the endpoint's actual shape (rule #3).
- 429 and
success:false + error-code responses are surfaced with their codes.
npm run typecheck and npm test pass in cod-server.
- The adapter test file covers create/validate/update/delete/remarks/tracking.
- No credentials, tokens, or tenant URLs in code or tests — only in
wrangler secrets / .dev.vars.
1---2name: ecotrack3description: EcoTrack platform integration for CodFlow — one API shared by 82 Algerian couriers (DHD, Conexlog, MSM Go, Packers, World Express...). Use when connecting an EcoTrack courier, auditing or fixing the adapter, verifying behavior against the official API, handling tracking/status issues, or extending EcoTrack support.4---56# EcoTrack Integration78EcoTrack is not a courier. It is the white-label platform a large slice of9Algeria's regional couriers run on: DHD, Conexlog, MSM Go, World Express,10Packers and ~80 others each expose the SAME API on their own `*.ecotrack.dz`11domain. **One adapter, 82 companies.** The companies differ (fleet, pricing,12delivery rate); the API does not.1314## Resources in this folder1516| File | What it holds |17|---|---|18| `about.md` | dzship's EcoTrack guide — the 82-courier key table + per-tenant quirks |19| `postman_collection.json` | The OFFICIAL API docs (Postman export) — the source of truth |20| `API-REFERENCE.md` | Every endpoint distilled from the Postman collection |21| `CODEBASE-MAP.md` | Where our integration lives, data flow, DB shape |22| `CONFORMANCE.md` | Verified audit: implemented ✓ / missing / wrong, verification checklist |23| `PLAN.md` | The slice-by-slice rollout plan for all 82 couriers — follow it top to bottom and keep its status table current |2425Read `API-REFERENCE.md` before writing any adapter code. Read `CONFORMANCE.md`26before assuming current behavior is correct. For the 82-courier rollout work,27`PLAN.md` is the operational document: pick the first slice marked NOT STARTED,28follow its checklist, verify green, mark it DONE.2930## The mental model3132```33delivery_companies row (code: "dhd_ecotrack", apiEndpoint: "https://dhd.ecotrack.dz", apiToken: "…")34 │35 ▼36isEcotrackCompany(code) → EcotrackProvider(apiToken, baseUrl)37 │38 ▼ one adapter, any *.ecotrack.dz tenant39POST /api/v1/create/order (query params, Bearer auth)40```4142- Company code MUST be `ecotrack` or end in `_ecotrack` — that suffix is what43 routes the company to this adapter (`registry.ts:32`).44- `apiEndpoint` holds the tenant base URL. There is no central EcoTrack host.45- `apiUserGuid` is unused (NOEST-only field).46- `autoValidate` defaults to **false** for the EcoTrack family — parcels wait47 as Dispatched until someone calls manual validation.4849## Workflow A — Connect a new EcoTrack courier50511. Find the courier key in `about.md`'s 82-courier table (e.g. `dhd`, `conexlog`, `msmgo`).522. Create the delivery company with:53 - `code` = `{key}_ecotrack` (or plain `ecotrack` for a generic tenant)54 - `apiEndpoint` = `https://{key}.ecotrack.dz` (the pattern; verify with the55 courier if unsure — generic fallback requires a `*.ecotrack.dz` host)56 - `apiToken` = token from that courier's dashboard (write-only; never logged)57 - `name`/`nameAr` per the courier's branding583. Decide `autoValidate` deliberately (default false for EcoTrack family —59 manual validation keeps control of when the parcel enters courier flow).604. Run stop-desk sync (admin action → `getStopDesks()` → `get/communes` filtered61 to `has_stop_desk === 1`, `code_postal` becomes the Station Code).625. Verify: token check, one test dispatch, label proxy, tracking pull.6364## Workflow B — Audit / verify the integration65661. Open `CONFORMANCE.md` — read the "Verified correct" and "Discrepancies" lists.672. Diff `adapter.ts` + `types.ts` against `API-REFERENCE.md`, endpoint by endpoint.683. Check response-shape handling for every endpoint you touch — EcoTrack shapes69 are NOT uniform (see Hard Rules #3).704. If you changed TypeScript: `cd cod-server && npm run typecheck`.715. If you changed behavior: `cd cod-server && npm test` — and note the EcoTrack72 adapter has NO test file (yalidine/noest/zr_express all have73 `adapter.test.ts`); when you touch the adapter, add/extend one using the same74 fetch-mock pattern.756. Live API verification only when the user explicitly provides a sandbox tenant76 and token. Never commit credentials. `.dev.vars` only.7778## Workflow C — Extend the adapter79801. Copy the exact param names / response shape from `API-REFERENCE.md` — do not81 guess, do not copy from another provider. EcoTrack uses French param names82 and three different response styles.832. Single-order endpoints take **query params, no JSON body**. Only84 `create/orders` (bulk) and `valid/returns` take a JSON body.853. Keep every response-shape guard defensive (`Array.isArray`, `?? []`) —86 tenants drift.874. Register nothing in the registry unless a new company family appears —88 `*_ecotrack` codes already route here.895. Add tests. Run typecheck + tests.9091## Hard rules (traps that bite)92931. **Auth is Bearer everywhere — EXCEPT `GET /api/v1/get/orders/status`**, which94 authenticates via an `api_token` **query param**. If you send only the95 Bearer header there, you get an auth error.962. **Rate limit: 50 requests/minute → HTTP 429** `{"message": "Too Many Attempts."}`.97 Auto-validate flows make 2 calls per order (create + valid) — a bulk dispatch98 of 25+ orders can hit the ceiling. Surface 429 distinctly; pace or back off.993. **Response shapes are not uniform.** Memorize:100 - `get/maj` → plain JSON **array**101 - `get/tracking/info` → **object** with `activity` array102 - `get/communes` → **object keyed by index** (never assume array)103 - `get/wilayas`, `get/orders` data → plain array / paginated object104 - `get/desks` → `{ my_desk: {...}, other_desks: [...] }`105 - bulk `create/orders` results → **keyed by `reference`** when the order had106 one, else by index string107 - Single create errors → HTTP 200 with `{success: false, error: 1000x}` OR108 HTTP 422 Laravel `errors` bag. Handle BOTH.1094. **`montant` is the COD amount INCLUDING delivery fees** (official param doc).110 CodFlow currently sends `order.price` (product subtotal) at111 `dispatch.ts` createShipment call — see CONFORMANCE.md before shipping a fix;112 confirm merchant intent (delivery fee charged to customer ⇒ must be included).1135. **`type` param**: 1=Livraison, 2=Échange, 3=PICKUP, 4=Recouvrement. Our114 adapter always sends `1`. `capabilities.ts` claiming `canExchange: false`115 contradicts the official docs — all four types are allowed.1166. **Update semantics**: officially all params optional, but Packers (one117 tenant) rejects calls missing `type, wilaya, commune, adresse, montant, tel`.118 The caller must pre-fill ALL fields from the order, then apply overrides —119 the route handler already does this; keep it that way.1207. **Update after validation is a silent no-op**: EcoTrack answers121 `success: true` but ignores the change (Packers-confirmed). We guard122 client-side (status must be `dispatched`). Never trust a post-validation123 update response.1248. **Delete only works pre-validation** (error 10001 after). Post-validation,125 the only carrier-side actions are `ask/for/order/return` (courier may126 IGNORE it) and `valid/returns` (confirming received returns).1279. **Labels are raw PDF bytes behind Bearer auth** — the URL is never public.128 Always serve through `proxyShipmentLabel` (server-side fetch), never expose129 the token to the browser.13010. **No webhooks.** Tracking is pull-only: `get/tracking/info` (single) and131 `get/trackings/info` (bulk, ≤100). The `/orders/:id/tracking` endpoint is132 the only freshness source for EcoTrack orders.13311. **Status wording drifts per tenant.** Never treat French display labels as134 stable identifiers. The stable vocabulary is the `activity` enum keys and135 the status enum keys (both listed in API-REFERENCE.md). An unknown label136 must surface as-is, never be guessed into a status.13712. **Tracking queries answer list-style** — a lazy "take the first row" client138 can attach the wrong parcel's status. Always query with the exact tracking139 number and match exactly.14013. **Validation limits**: phone 9–10 digits numeric; wilaya code 1–58 integer;141 every string field max 255 chars. `commune` is a NAME (string), matched142 against the tenant's enabled commune list — a wrong/misspelled commune name143 is a 422, and a disabled wilaya returns error 10002.14414. **Bulk endpoint reliability is per-tenant**: Packers' `create/orders`145 returned HTTP 500 (server-side bug, documented in `dispatch.ts`). Verify on146 the specific tenant before relying on bulk dispatch; fall back to147 sequential single creates.14815. **The `_ecotrack` suffix is load-bearing**: adapter selection, the149 autoValidate default, and the update guard all key off150 `isEcotrackCompany(code)`. A company code without the suffix silently loses151 all three.152153## What "done right" looks like154155- Every param name matches `API-REFERENCE.md` exactly (French, snake_case).156- Every response parse handles the endpoint's actual shape (rule #3).157- 429 and `success:false` + error-code responses are surfaced with their codes.158- `npm run typecheck` and `npm test` pass in cod-server.159- The adapter test file covers create/validate/update/delete/remarks/tracking.160- No credentials, tokens, or tenant URLs in code or tests — only in161 `wrangler secrets` / `.dev.vars`.