COD Pro API

Base URL is your deployed origin (e.g. https://your-domain.com). All JSON bodies use Content-Type: application/json unless noted.

Authentication (browser session)

Dashboard APIs use a cookie session (cod.sid). From JavaScript, send credentials: "include" on fetch requests.

MethodPathDescription
POST /login Body: { "username", "password" }. Returns { success, redirect: "/embedded" } on success; sets session cookie.
POST /logout Destroys session. Returns { success: true }.

Webhook (server-to-server)

If WEBHOOK_SECRET is set in the environment, each request must send the secret as Authorization: Bearer <secret> or X-Api-Key: <secret>.

Required body: customer.phone (string). Typical payload also includes customer (name, email, city, address, …), product, total, utm, notes, optional id (otherwise generated).

Multi-tenant routing: each stored order has business_id (nullable). The server resolves it from (in order) business_id, business_slug, hostname of page_url, or hostname of shop (e.g. store.myshopify.com) against the hosts list configured per business in the admin UI. Unmatched orders stay business_id: null (admin-only visibility).

{
  "id": "optional-stable-id",
  "page_url": "https://your-store.com/products/item",
  "shop": "optional.myshopify.com",
  "business_slug": "optional-if-you-use-slugs",
  "customer": {
    "name": "Jane",
    "phone": "+212600000000",
    "city": "Casablanca",
    "address": "…"
  },
  "product": { "name": "Item", "price": 199, "quantity": 1 },
  "total": 199,
  "currency": "MAD",
  "utm": { "source": "facebook" }
}

Multi-tenancy & roles

Admin sees all businesses and unassigned orders. Support users only see orders whose business_id is one of the businesses assigned to them (many-to-many in SQLite). Support users with no assignment see an empty order list.

List endpoints accept optional ?business_id=<id> to filter. Use business_id=unassigned (admin) to list orders with no business. Omit or use all for no filter.

REST API (session required)

All routes below require a valid session except where noted. Admin-only actions need role: "admin" on the account.

MethodPathAuthNotes
GET /api/me Session { userId, username, role, businesses[], unassignedCount } — businesses is scoped to the user (all for admin, assigned for support).
GET /api/orders Session Scoped by role; optional ?business_id=. Active orders only by default. Admin: ?include_deleted=1, ?deleted_only=1 (trash). Support never receives deleted rows.
PATCH /api/orders/:id Session Body: customer fields, agent_notes. Updates order in place.
POST /api/orders/:id/status Session Body: { "status": "pending"|"confirmed"|"shipped"|"delivered"|"cancelled" }
DELETE /api/orders/:id Admin Soft-delete: sets deleted_at (moves to trash).
POST /api/orders/:id/restore Admin Clears deleted_at for that order.
GET /api/stats Session Aggregates for active orders in scope; same ?business_id= filter as /api/orders.
GET /api/businesses Admin List businesses (id, slug, name, hosts).
POST /api/businesses Admin Body: { slug, name, hosts: ["domain.com", …] }.
PATCH /api/businesses/:id Admin Partial update: slug, name, hosts.
DELETE /api/businesses/:id Admin Deletes business row (assignments cleared via FK).
GET /api/users Admin Users with businessIds for support accounts.
POST /api/users Admin Body: { username, password } — creates a support user.
PATCH /api/users/:id/businesses Admin Body: { businessIds: number[] } — replaces support user’s business assignments (ignored for admin users).
GET /api/olivraison/config Session Public-safe Olivraison flags (no raw secrets).
POST /api/orders/:id/olivraison/push Session Creates package when Olivraison keys are configured.
POST /api/orders/:id/olivraison/sync Session Pulls remote package status.
POST /api/olivraison/sync-all Session Body or query limit (max 100). Bulk sync tracked orders.

Health (no session)

GET /health — JSON with status, uptime, version, DB check, Olivraison configured flag. Intended for monitors; do not expose sensitive data publicly without network controls.

Rate limits

Login, webhook, and /api/* routes use express-rate-limit (see server configuration). Expect 429 when exceeded.

Olivraison troubleshooting

Run npm run test:oliv from the project root (loads .env). It calls POST /auth/login and GET /status_list. If login returns code: "002" / “Client not found”, the API key or secret is wrong or the partner account is inactive — copy keys from the Olivraison partner dashboard.

Package creation returns a tracking id under trackingID or alternate field names; the server normalizes these. If push still fails, check the JSON error body from Olivraison (destination blacklisted, missing city, invalid pickup JSON, etc.).