OpenAPI 3.1Version v1 8 endpoints · 8 webhook events

Delivery Syria Company API

Read your merchants, couriers and parcels, create parcels for your merchants, assign couriers and update statuses.

Import this file into Postman, Insomnia or any OpenAPI 3.1 tool, or give the tool the address below. The server in the file is relative (it has no host): after importing, set your platform address as the base URL.

URL
https://deliverysyria.com/docs/api/company-v1.json

Overview

The company API (/api/company/v1) lets the system of a delivery company read its merchants, couriers and parcels, create parcels for its merchants, assign couriers and update parcel statuses. Everything is limited to your company. Every call is authenticated with a Bearer API key and every answer is JSON in the same envelope: {"success": true, "data": ...} (plus meta on paginated lists) or {"success": false, "error": {"code", "message"}}. Always send Accept: application/json.

This document was written from the application code and is checked against the real routes, validation rules and response shapes by automated tests. It describes only what the code does. There is no versioning header, no Idempotency-Key support and no "try it out" console, and the company API has no endpoints to manage webhook links (use the company panel). Rate limits are described on each operation: requests per minute per IP address, plus the limits of your plan.

For guides and explanations read the developer articles of the documentation center.

Base URL
https://YOUR_DOMAIN/api/company/v1 Replace YOUR_DOMAIN with your platform address.
Authentication

The API key of your delivery company in the Authorization header: Authorization: Bearer YOUR_COMPANY_API_KEY. Only this header is read; a key in the query string or in the body is ignored. A company admin creates the key in the company panel (Integrations and API). It works only on /api/company/v1: a merchant key gives invalid_key. WooCommerce ck_... consumer keys are a different thing and never work here.

HTTP
Authorization: Bearer YOUR_COMPANY_API_KEY
Accept-Language

Language of error messages and labels (status labels, event notes, names). If the value starts with en the answer is in English; any other value or no header gives Arabic. Machine-readable fields never change.

Rate limits

Three layers apply. (1) A general limit of 60 requests per minute per IP address on every /api request. (2) Route limits, counted per IP address on ONE shared counter: the group limit of 300 per minute, and 120 for POST /parcels; because a POST /parcels request is counted by both the group and the route, one IP address can create at most about 60 parcels per minute. (3) Plan limits per company, per minute and per day (429 rate_limited with Retry-After). The limits that apply to each operation are listed on that operation.

No “Try it out” console

This reference has no “Try it out” console and sends no request from your browser. That would mean typing a live API key into a web page and calling the production API with production data. Test with the code samples and your own key from your own server or terminal instead.

Account

Your company, plan and usage.

Method GET /me #

Get your company account

Returns your company, active plan (with its features) and the usage rows against the plan limits. A good first call to check the key.

Responses

200 Your company account.

Your company account.

Headers: X-RateLimit-Limit X-RateLimit-Remaining

FieldType RequiredDescription
success boolean required

constant true

data object required

Your company account.

company object required

Your company.

id integer required

Company id.

name string | null required

Company name.

phone string | null required

Company phone.

plan object | null required

Active subscription, or null when there is none.

name string | null required

Plan name.

expires_at string required

Subscription expiry (YYYY-MM-DD HH:MM:SS).

features array<object> required

Plan features.

feature string required

Display label in the request language (it can include the limits, such as the API per-minute and per-day figures).

enabled boolean required

Whether the plan includes it.

usage array<object> required

Usage against the plan limits.

name string required

What is counted, in the request language (for example API requests today, webhook URLs, merchants).

used integer required

Amount used.

limit integer | null required

The limit, or null when unlimited.

Example

JSON · 200
{
  "success": true,
  "data": {
    "company": {
      "id": 3,
      "name": "Example Delivery",
      "phone": "0900000000"
    },
    "plan": {
      "name": "Example Plan",
      "expires_at": "2026-12-01 00:00:00",
      "features": [
        {
          "feature": "Company API — 120 per minute / 20,000 per day",
          "enabled": true
        }
      ]
    },
    "usage": [
      {
        "name": "API requests today",
        "used": 1240,
        "limit": 20000
      }
    ]
  }
}

401 Missing or unknown API key. common response, see below

402 The company subscription has ended (subscription_expired). common response, see below

403 Access refused. common response, see below

429 Rate limit. common response, see below

500 Unexpected server error. common response, see below

Code samples

curl
curl
curl -X GET "https://YOUR_DOMAIN/api/company/v1/me" \
  -H "Authorization: Bearer YOUR_COMPANY_API_KEY" \
  -H "Accept: application/json"
PHP
PHP
<?php
$ch = curl_init("https://YOUR_DOMAIN/api/company/v1/me");
curl_setopt_array($ch, [
    CURLOPT_CUSTOMREQUEST  => "GET",
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_TIMEOUT        => 30,
    CURLOPT_HTTPHEADER     => [
        "Authorization: Bearer " . getenv("DS_COMPANY_API_KEY"),
        "Accept: application/json",
    ],
]);
$body   = json_decode(curl_exec($ch), true);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);

if (!($body["success"] ?? false)) {
    throw new RuntimeException("$status {$body["error"]["code"]}: {$body["error"]["message"]}");
}
print_r($body["data"]);
JavaScript
JavaScript
const response = await fetch("https://YOUR_DOMAIN/api/company/v1/me", {
  method: "GET",
  headers: {
    "Authorization": `Bearer ${process.env.DS_COMPANY_API_KEY}`,
    "Accept": "application/json",
  },
});
const json = await response.json();
if (!json.success) {
  throw new Error(`${response.status} ${json.error.code}: ${json.error.message}`);
}
console.log(json.data);
Python
Python
import os
import requests

response = requests.get(
    "https://YOUR_DOMAIN/api/company/v1/me",
    headers={
        "Authorization": f"Bearer {os.environ['DS_COMPANY_API_KEY']}",
        "Accept": "application/json",
    },
    timeout=30,
)
body = response.json()
if not body["success"]:
    raise RuntimeError(f"{response.status_code} {body['error']['code']}: {body['error']['message']}")
print(body["data"])

Merchants

Your merchants and their pickup locations.

Method GET /merchants #

List your merchants

Your merchants with their pickup locations, ordered by id. Paginated: the answer has meta.

Query parameters

NameTypeRequiredDescription
q string optional

Part of the business name or of the mobile number.

Example Example

per_page integer optional

Page size, from 1 to 100 (default 50). A value outside the range is not rejected: it is clamped to the nearest limit and the effective value comes back in meta.per_page.

minimum 1maximum 100default 50

page integer optional

Page number, starting at 1. An invalid value or a value below 1 is treated as 1. A page beyond the last one returns an empty list.

minimum 1default 1

Responses

200 A page of merchants.

A page of merchants.

Headers: X-RateLimit-Limit X-RateLimit-Remaining

FieldType RequiredDescription
success boolean required

constant true

data array<object> required
id integer required

Send it as merchant_id when you create a parcel.

business_name string required

Business name.

contact_name string | null required

Contact person.

phone string | null required

Mobile number.

email string | null required

Email address.

active boolean required

Whether the merchant is active.

pickup_locations array<object> required

The merchant's pickup locations.

id integer required

Send it as pickup_location_id.

name string | null required

Location name.

address string | null required

Address.

phone string | null required

Contact phone.

default_shop integer required

Default flag as 0 or 1 (same meaning as default).

One of: 0 1

default boolean required

True for the merchant's default location.

meta Meta required

Pagination block. There are no links and no cursor.

Example

JSON · 200
{
  "success": true,
  "data": [
    {
      "id": 41,
      "business_name": "Example Shop",
      "contact_name": "Example Name",
      "phone": "0900000000",
      "email": "shop@example.com",
      "active": true,
      "pickup_locations": [
        {
          "id": 7,
          "name": "Main branch",
          "address": "Example street, building 1",
          "phone": "0900000000",
          "default_shop": 1,
          "default": true
        }
      ]
    }
  ],
  "meta": {
    "page": 1,
    "per_page": 20,
    "total": 1,
    "last_page": 1
  }
}

401 Missing or unknown API key. common response, see below

402 The company subscription has ended (subscription_expired). common response, see below

403 Access refused. common response, see below

429 Rate limit. common response, see below

500 Unexpected server error. common response, see below

Code samples

curl
curl
curl -X GET "https://YOUR_DOMAIN/api/company/v1/merchants?q=Example" \
  -H "Authorization: Bearer YOUR_COMPANY_API_KEY" \
  -H "Accept: application/json"
PHP
PHP
<?php
$ch = curl_init("https://YOUR_DOMAIN/api/company/v1/merchants?q=Example");
curl_setopt_array($ch, [
    CURLOPT_CUSTOMREQUEST  => "GET",
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_TIMEOUT        => 30,
    CURLOPT_HTTPHEADER     => [
        "Authorization: Bearer " . getenv("DS_COMPANY_API_KEY"),
        "Accept: application/json",
    ],
]);
$body   = json_decode(curl_exec($ch), true);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);

if (!($body["success"] ?? false)) {
    throw new RuntimeException("$status {$body["error"]["code"]}: {$body["error"]["message"]}");
}
print_r($body["data"]);
JavaScript
JavaScript
const response = await fetch("https://YOUR_DOMAIN/api/company/v1/merchants?q=Example", {
  method: "GET",
  headers: {
    "Authorization": `Bearer ${process.env.DS_COMPANY_API_KEY}`,
    "Accept": "application/json",
  },
});
const json = await response.json();
if (!json.success) {
  throw new Error(`${response.status} ${json.error.code}: ${json.error.message}`);
}
console.log(json.data);
Python
Python
import os
import requests

response = requests.get(
    "https://YOUR_DOMAIN/api/company/v1/merchants",
    headers={
        "Authorization": f"Bearer {os.environ['DS_COMPANY_API_KEY']}",
        "Accept": "application/json",
    },
    params={
        "q": "Example",
    },
    timeout=30,
)
body = response.json()
if not body["success"]:
    raise RuntimeError(f"{response.status_code} {body['error']['code']}: {body['error']['message']}")
print(body["data"])

Couriers

Your couriers (deliverymen) with their load.

Method GET /deliverymen #

List your couriers

All your couriers with their current load. The list is not paginated.

  • Request GET /api/company/v1/deliverymen
  • Rate limit per minute, per IP address 60 overall, 300 for the whole API
  • Requires plan feature: API (checked when plans apply to your account)
  • Guides Company API v1 reference

Responses

200 Your couriers.

Your couriers.

Headers: X-RateLimit-Limit X-RateLimit-Remaining

FieldType RequiredDescription
success boolean required

constant true

data array<object> required
id integer required

Send it as deliveryman_id.

name string | null required

Courier name.

phone string | null required

Courier mobile number.

active boolean required

Whether the courier account is active.

open_parcels integer required

Parcels in status Delivery Man Assign or Delivery Re-Schedule that have an event for this courier: an approximation of what the courier holds (the assign dialog in the panel counts the same way).

parcel_limit integer required

The courier's parcel limit (5 when none is set).

online boolean required

The courier's duty flag (connected to the app).

available boolean required

False when the courier is not active, or when your company requires couriers to be online and this one is not.

Example

JSON · 200
{
  "success": true,
  "data": [
    {
      "id": 5,
      "name": "Example Courier",
      "phone": "0900000001",
      "active": true,
      "open_parcels": 12,
      "parcel_limit": 50,
      "online": true,
      "available": true
    }
  ]
}

401 Missing or unknown API key. common response, see below

402 The company subscription has ended (subscription_expired). common response, see below

403 Access refused. common response, see below

429 Rate limit. common response, see below

500 Unexpected server error. common response, see below

Code samples

curl
curl
curl -X GET "https://YOUR_DOMAIN/api/company/v1/deliverymen" \
  -H "Authorization: Bearer YOUR_COMPANY_API_KEY" \
  -H "Accept: application/json"
PHP
PHP
<?php
$ch = curl_init("https://YOUR_DOMAIN/api/company/v1/deliverymen");
curl_setopt_array($ch, [
    CURLOPT_CUSTOMREQUEST  => "GET",
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_TIMEOUT        => 30,
    CURLOPT_HTTPHEADER     => [
        "Authorization: Bearer " . getenv("DS_COMPANY_API_KEY"),
        "Accept: application/json",
    ],
]);
$body   = json_decode(curl_exec($ch), true);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);

if (!($body["success"] ?? false)) {
    throw new RuntimeException("$status {$body["error"]["code"]}: {$body["error"]["message"]}");
}
print_r($body["data"]);
JavaScript
JavaScript
const response = await fetch("https://YOUR_DOMAIN/api/company/v1/deliverymen", {
  method: "GET",
  headers: {
    "Authorization": `Bearer ${process.env.DS_COMPANY_API_KEY}`,
    "Accept": "application/json",
  },
});
const json = await response.json();
if (!json.success) {
  throw new Error(`${response.status} ${json.error.code}: ${json.error.message}`);
}
console.log(json.data);
Python
Python
import os
import requests

response = requests.get(
    "https://YOUR_DOMAIN/api/company/v1/deliverymen",
    headers={
        "Authorization": f"Bearer {os.environ['DS_COMPANY_API_KEY']}",
        "Accept": "application/json",
    },
    timeout=30,
)
body = response.json()
if not body["success"]:
    raise RuntimeError(f"{response.status_code} {body['error']['code']}: {body['error']['message']}")
print(body["data"])

Parcels

List and read parcels, create a parcel for a merchant, assign a courier and update the status.

Method GET /parcels #

List your parcels

Lists the parcels of your company, newest first (by id), without events. Paginated: the answer has meta.

Query parameters

NameTypeRequiredDescription
merchant_id integer optional

Only the parcels of this merchant.

Example 41

deliveryman_id integer optional

Only the parcels held by this courier.

Example 5

reference string optional

Exact match on the merchant's reference.

Example ORDER-1043

from string (date) optional

First creation day, YYYY-MM-DD, inclusive. The format is not validated: use only this one.

Example 2026-01-01

to string (date) optional

Last creation day, YYYY-MM-DD, inclusive. The format is not validated: use only this one.

Example 2026-01-31

updated_since string optional

Only parcels updated at or after this time, YYYY-MM-DD HH:MM:SS. Useful for periodic synchronisation.

Example 2026-01-01 10:00:00

stage string optional

Filter by stage. Unlike the merchant API an unknown value is rejected with 422 validation_failed. processing matches only the status codes 2, 3, 4, 5, 6 and 19, so some parcels whose stage is processing are not returned by this filter. awaiting_funds is not accepted.

One of: pending processing out_for_delivery delivered partially_delivered returning returned

Example processing

per_page integer optional

Page size, from 1 to 100 (default 20). A value outside the range is not rejected: it is clamped to the nearest limit and the effective value comes back in meta.per_page.

minimum 1maximum 100default 20

page integer optional

Page number, starting at 1. An invalid value or a value below 1 is treated as 1. A page beyond the last one returns an empty list.

minimum 1default 1

Responses

200 A page of parcels.

A page of parcels.

Headers: X-RateLimit-Limit X-RateLimit-Remaining

FieldType RequiredDescription
success boolean required

constant true

data array<Parcel> required
meta Meta required

Pagination block. There are no links and no cursor.

Example

JSON · 200
{
  "success": true,
  "data": [
    {
      "id": 1043,
      "tracking_number": "DS-000000",
      "reference": "ORDER-1043",
      "stage": "out_for_delivery",
      "status_code": 7,
      "status_label": "Delivery Man Assign",
      "company_id": 3,
      "company_name": "Example Delivery",
      "external_tracking_number": null,
      "recipient": {
        "name": "Example Name",
        "phone": "0900000000",
        "governorate": "Damascus",
        "address": "Example street, building 1",
        "lat": null,
        "lng": null
      },
      "cod_amount": 25,
      "cod_collected": 0,
      "weight_kg": 1.5,
      "charges": {
        "delivery_fee": 6,
        "platform_fee": 1
      },
      "delivery_attempts": 0,
      "note": null,
      "tracking_url": "https://YOUR_DOMAIN/t/DS-000000",
      "created_at": "2026-01-01T10:00:00+00:00",
      "updated_at": "2026-01-01T12:10:00+00:00",
      "merchant": {
        "id": 41,
        "name": "Example Shop"
      },
      "deliveryman": {
        "id": 5,
        "name": "Example Courier"
      }
    }
  ],
  "meta": {
    "page": 1,
    "per_page": 20,
    "total": 1,
    "last_page": 1
  }
}

401 Missing or unknown API key. common response, see below

402 The company subscription has ended (subscription_expired). common response, see below

403 Access refused. common response, see below

422 validation_failed:

validation_failed: unknown stage. The message lists the available values; there is no fields object.

Headers: X-RateLimit-Limit X-RateLimit-Remaining

Example — Unknown stage

JSON · 422
{
  "success": false,
  "error": {
    "code": "validation_failed",
    "message": "stage is unknown. Available: pending، processing، out_for_delivery، delivered، partially_delivered، returning، returned"
  }
}

429 Rate limit. common response, see below

500 Unexpected server error. common response, see below

Code samples

curl
curl
curl -X GET "https://YOUR_DOMAIN/api/company/v1/parcels?merchant_id=41&deliveryman_id=5&reference=ORDER-1043&from=2026-01-01" \
  -H "Authorization: Bearer YOUR_COMPANY_API_KEY" \
  -H "Accept: application/json"
PHP
PHP
<?php
$ch = curl_init("https://YOUR_DOMAIN/api/company/v1/parcels?merchant_id=41&deliveryman_id=5&reference=ORDER-1043&from=2026-01-01");
curl_setopt_array($ch, [
    CURLOPT_CUSTOMREQUEST  => "GET",
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_TIMEOUT        => 30,
    CURLOPT_HTTPHEADER     => [
        "Authorization: Bearer " . getenv("DS_COMPANY_API_KEY"),
        "Accept: application/json",
    ],
]);
$body   = json_decode(curl_exec($ch), true);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);

if (!($body["success"] ?? false)) {
    throw new RuntimeException("$status {$body["error"]["code"]}: {$body["error"]["message"]}");
}
print_r($body["data"]);
JavaScript
JavaScript
const response = await fetch("https://YOUR_DOMAIN/api/company/v1/parcels?merchant_id=41&deliveryman_id=5&reference=ORDER-1043&from=2026-01-01", {
  method: "GET",
  headers: {
    "Authorization": `Bearer ${process.env.DS_COMPANY_API_KEY}`,
    "Accept": "application/json",
  },
});
const json = await response.json();
if (!json.success) {
  throw new Error(`${response.status} ${json.error.code}: ${json.error.message}`);
}
console.log(json.data);
Python
Python
import os
import requests

response = requests.get(
    "https://YOUR_DOMAIN/api/company/v1/parcels",
    headers={
        "Authorization": f"Bearer {os.environ['DS_COMPANY_API_KEY']}",
        "Accept": "application/json",
    },
    params={
        "merchant_id": "41",
        "deliveryman_id": "5",
        "reference": "ORDER-1043",
        "from": "2026-01-01",
    },
    timeout=30,
)
body = response.json()
if not body["success"]:
    raise RuntimeError(f"{response.status_code} {body['error']['code']}: {body['error']['message']}")
print(body["data"])

Method POST /parcels #

Create a parcel for one of your merchants

Creates a parcel on behalf of one of your merchants. The fee uses your company's pricing; nothing is taken from platform wallets. The same reference for the same merchant gives 409 duplicate_reference whatever the status of the earlier parcel (treat it as "already created"); without a reference every call creates a new parcel. The governorate or the coordinates are required according to your company's pricing mode.

Request body

application/json · required

FieldType RequiredDescription
merchant_id integer required

The merchant the parcel is created for: an id from GET /merchants. It must be a merchant of your company.

recipient object required

The recipient. Each field may instead be sent flat at the top level (customer_name, customer_phone, customer_address, customer_city, lat, long); the nested form is the documented one.

name string required

Recipient name.

max length 191

phone string required

Recipient phone number: digits, spaces, +, - and parentheses, 6 to 30 characters.

max length 30pattern ^[+0-9\s\-()]{6,30}$

address string required

Recipient address.

max length 1000

governorate string optional

Governorate name (English name or Arabic name_ar from GET /governorates). If you send the id, send it as a string such as "3": a JSON number is rejected. Needed for governorate pricing; otherwise the platform tries to find it from the address, then from the coordinates.

max length 120also accepted as customer_city, city

lat number optional

Recipient latitude. Required for distance pricing (per_km, km_kg).

minimum -90maximum 90

lng number optional

Recipient longitude. Required for distance pricing (per_km, km_kg).

minimum -180maximum 180also accepted as long

reference string optional

The merchant's own reference (order number), up to 191 characters. A reference the merchant already used gives 409 duplicate_reference; unlike the merchant API the old parcel is not returned.

max length 191also accepted as invoice_no

cod_amount number optional

Cash-on-delivery amount the recipient pays, from 0 to 100,000,000 US dollars (a non-USD amount is converted to USD at the platform rate before the limit is checked). 0 means prepaid.

minimum 0maximum 100000000default 0

currency string optional

Currency code of cod_amount (case-insensitive). The amount is stored in US dollars, so cod_amount in parcel objects is always USD. A company may accept only some currencies. A missing exchange rate gives 422.

One of: USD SYP SAR AED QAR KWD JOD IQD EGP LBP TRY EUR

default USDalso accepted as cod_currency

weight number optional

Weight in kilograms, from 0.01 to 1000. Default 1. For internal pricing the weight is rounded up to a whole kilogram (minimum 1).

minimum 0.01maximum 1000default 1also accepted as weight_kg

pickup_location_id integer optional

One of the merchant's pickup locations (pickup_locations in GET /merchants). The merchant's default location is used when omitted. Errors are reported on the field shop_id.

also accepted as shop_id

note string optional

Note for the delivery company, up to 2000 characters.

max length 2000

shipping_paid_by string optional

Who pays the shipping fee, for carriers that support the choice: sender, customer (the recipient) or included_in_cod.

One of: sender customer included_in_cod

Example

JSON
{
  "merchant_id": 41,
  "reference": "ORDER-1043",
  "recipient": {
    "name": "Example Name",
    "phone": "0900000000",
    "governorate": "Damascus",
    "address": "Example street, building 1"
  },
  "cod_amount": 25,
  "weight": 1
}

Responses

201 The parcel was created.

The parcel was created.

Headers: X-RateLimit-Limit X-RateLimit-Remaining

FieldType RequiredDescription
success boolean required

constant true

data Parcel required

A parcel as returned by the API and carried by webhooks (same fields every time).

Example

JSON · 201
{
  "success": true,
  "data": {
    "id": 1043,
    "tracking_number": "DS-000000",
    "reference": "ORDER-1043",
    "stage": "processing",
    "status_code": 5,
    "status_label": "Received Warehouse",
    "company_id": 3,
    "company_name": "Example Delivery",
    "external_tracking_number": null,
    "recipient": {
      "name": "Example Name",
      "phone": "0900000000",
      "governorate": "Damascus",
      "address": "Example street, building 1",
      "lat": null,
      "lng": null
    },
    "cod_amount": 25,
    "cod_collected": 0,
    "weight_kg": 1.5,
    "charges": {
      "delivery_fee": 6,
      "platform_fee": 1
    },
    "delivery_attempts": 0,
    "note": null,
    "tracking_url": "https://YOUR_DOMAIN/t/DS-000000",
    "created_at": "2026-01-01T10:00:00+00:00",
    "updated_at": "2026-01-01T12:10:00+00:00",
    "merchant": {
      "id": 41,
      "name": "Example Shop"
    },
    "deliveryman": null
  }
}

401 Missing or unknown API key. common response, see below

402 insufficient_balance:

insufficient_balance: the balance is not enough for the parcel fee. subscription_expired: your company subscription has ended.

Headers: X-RateLimit-Limit X-RateLimit-Remaining

Example — Balance too low

JSON · 402
{
  "success": false,
  "error": {
    "code": "insufficient_balance",
    "message": "Insufficient wallet balance. Required: 7.00, available: 2.00"
  }
}

Example — Subscription ended

JSON · 402
{
  "success": false,
  "error": {
    "code": "subscription_expired",
    "message": "Company subscription expired — renew the subscription from the company dashboard."
  }
}

403 Access refused. common response, see below

409 duplicate_reference:

duplicate_reference: the merchant already has a parcel with this reference. Fetch it with GET /parcels?merchant_id=<id>&reference=<reference> (a bare GET /parcels/{reference} searches all your merchants).

Headers: X-RateLimit-Limit X-RateLimit-Remaining

Example — Reference already used

JSON · 409
{
  "success": false,
  "error": {
    "code": "duplicate_reference",
    "message": "A parcel with the same reference already exists for this merchant — use GET /parcels/ORDER-1043."
  }
}
422 validation_failed:

validation_failed: merchant_id missing or not a merchant of your company (the error is on the field merchant_id), or another field is missing or wrong. carrier_error: the carrier could not process the parcel.

Headers: X-RateLimit-Limit X-RateLimit-Remaining

Example — Unknown merchant

JSON · 422
{
  "success": false,
  "error": {
    "code": "validation_failed",
    "message": "merchant_id is required and must be a merchant of your company (GET /merchants).",
    "fields": {
      "merchant_id": [
        "Merchant not found in your company."
      ]
    }
  }
}

Example — Carrier refused

JSON · 422
{
  "success": false,
  "error": {
    "code": "carrier_error",
    "message": "Localized message from the carrier."
  }
}

429 Rate limit. common response, see below

500 Unexpected server error. common response, see below

Code samples

curl
curl
curl -X POST "https://YOUR_DOMAIN/api/company/v1/parcels" \
  -H "Authorization: Bearer YOUR_COMPANY_API_KEY" \
  -H "Accept: application/json" \
  -H "Content-Type: application/json" \
  -d '{
  "merchant_id": 41,
  "reference": "ORDER-1043",
  "recipient": {
    "name": "Example Name",
    "phone": "0900000000",
    "governorate": "Damascus",
    "address": "Example street, building 1"
  },
  "cod_amount": 25,
  "weight": 1
}'
PHP
PHP
<?php
$ch = curl_init("https://YOUR_DOMAIN/api/company/v1/parcels");
curl_setopt_array($ch, [
    CURLOPT_CUSTOMREQUEST  => "POST",
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_TIMEOUT        => 30,
    CURLOPT_HTTPHEADER     => [
        "Authorization: Bearer " . getenv("DS_COMPANY_API_KEY"),
        "Accept: application/json",
        "Content-Type: application/json",
    ],
    CURLOPT_POSTFIELDS     => json_encode([
        'merchant_id' => 41,
        'reference' => 'ORDER-1043',
        'recipient' => [
            'name' => 'Example Name',
            'phone' => '0900000000',
            'governorate' => 'Damascus',
            'address' => 'Example street, building 1',
        ],
        'cod_amount' => 25,
        'weight' => 1,
    ]),
]);
$body   = json_decode(curl_exec($ch), true);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);

if (!($body["success"] ?? false)) {
    throw new RuntimeException("$status {$body["error"]["code"]}: {$body["error"]["message"]}");
}
print_r($body["data"]);
JavaScript
JavaScript
const response = await fetch("https://YOUR_DOMAIN/api/company/v1/parcels", {
  method: "POST",
  headers: {
    "Authorization": `Bearer ${process.env.DS_COMPANY_API_KEY}`,
    "Accept": "application/json",
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    "merchant_id": 41,
    "reference": "ORDER-1043",
    "recipient": {
      "name": "Example Name",
      "phone": "0900000000",
      "governorate": "Damascus",
      "address": "Example street, building 1"
    },
    "cod_amount": 25,
    "weight": 1
  }),
});
const json = await response.json();
if (!json.success) {
  throw new Error(`${response.status} ${json.error.code}: ${json.error.message}`);
}
console.log(json.data);
Python
Python
import os
import requests

response = requests.post(
    "https://YOUR_DOMAIN/api/company/v1/parcels",
    headers={
        "Authorization": f"Bearer {os.environ['DS_COMPANY_API_KEY']}",
        "Accept": "application/json",
    },
    json={
        "merchant_id": 41,
        "reference": "ORDER-1043",
        "recipient": {
            "name": "Example Name",
            "phone": "0900000000",
            "governorate": "Damascus",
            "address": "Example street, building 1",
        },
        "cod_amount": 25,
        "weight": 1,
    },
    timeout=30,
)
body = response.json()
if not body["success"]:
    raise RuntimeError(f"{response.status_code} {body['error']['code']}: {body['error']['message']}")
print(body["data"])

Method GET /parcels/{tracking} #

Get one parcel with its events

Takes a tracking number or a reference and returns the parcel with events in time order.

  • Request GET /api/company/v1/parcels/{tracking}
  • Rate limit per minute, per IP address 60 overall, 300 for the whole API
  • Requires plan feature: API (checked when plans apply to your account)
  • Guides Company API v1 reference

Path parameters

NameTypeRequiredDescription
tracking string required

A tracking number, or the reference the merchant sent (the newest match among the parcels of ALL your merchants wins, so two merchants that share a reference value can give you the other merchant's parcel: prefer the tracking number).

Example DS-000000

Responses

200 The parcel with its events.

The parcel with its events.

Headers: X-RateLimit-Limit X-RateLimit-Remaining

FieldType RequiredDescription
success boolean required

constant true

data object required

The parcel object plus its history (events). Returned when one parcel is fetched.

Includes every field of Parcel
events array<object> required

History of the parcel in time order. Present only when one parcel is fetched.

status_code integer required

Exact internal status code of that step.

stage string required

Simplified stage of that step.

One of: pending processing out_for_delivery delivered partially_delivered returning returned

status_label string required

Status label in the request language.

note string | null required

Free-text note recorded with the event.

at string (date-time) | null required

Time of the event (ISO 8601 with a UTC offset).

Example

JSON · 200
{
  "success": true,
  "data": {
    "id": 1043,
    "tracking_number": "DS-000000",
    "reference": "ORDER-1043",
    "stage": "out_for_delivery",
    "status_code": 7,
    "status_label": "Delivery Man Assign",
    "company_id": 3,
    "company_name": "Example Delivery",
    "external_tracking_number": null,
    "recipient": {
      "name": "Example Name",
      "phone": "0900000000",
      "governorate": "Damascus",
      "address": "Example street, building 1",
      "lat": null,
      "lng": null
    },
    "cod_amount": 25,
    "cod_collected": 0,
    "weight_kg": 1.5,
    "charges": {
      "delivery_fee": 6,
      "platform_fee": 1
    },
    "delivery_attempts": 0,
    "note": null,
    "tracking_url": "https://YOUR_DOMAIN/t/DS-000000",
    "created_at": "2026-01-01T10:00:00+00:00",
    "updated_at": "2026-01-01T12:10:00+00:00",
    "merchant": {
      "id": 41,
      "name": "Example Shop"
    },
    "deliveryman": {
      "id": 5,
      "name": "Example Courier"
    },
    "events": [
      {
        "status_code": 1,
        "stage": "pending",
        "status_label": "Pending",
        "note": "Parcel created (api)",
        "at": "2026-01-01T10:00:00+00:00"
      },
      {
        "status_code": 5,
        "stage": "processing",
        "status_label": "Received Warehouse",
        "note": null,
        "at": "2026-01-01T10:00:01+00:00"
      }
    ]
  }
}

401 Missing or unknown API key. common response, see below

402 The company subscription has ended (subscription_expired). common response, see below

403 Access refused. common response, see below

404 not_found:

not_found: no parcel with this tracking number or reference at your company.

Headers: X-RateLimit-Limit X-RateLimit-Remaining

Example — Unknown parcel

JSON · 404
{
  "success": false,
  "error": {
    "code": "not_found",
    "message": "No parcel with this number in your company."
  }
}

429 Rate limit. common response, see below

500 Unexpected server error. common response, see below

Code samples

curl
curl
curl -X GET "https://YOUR_DOMAIN/api/company/v1/parcels/DS-000000" \
  -H "Authorization: Bearer YOUR_COMPANY_API_KEY" \
  -H "Accept: application/json"
PHP
PHP
<?php
$ch = curl_init("https://YOUR_DOMAIN/api/company/v1/parcels/DS-000000");
curl_setopt_array($ch, [
    CURLOPT_CUSTOMREQUEST  => "GET",
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_TIMEOUT        => 30,
    CURLOPT_HTTPHEADER     => [
        "Authorization: Bearer " . getenv("DS_COMPANY_API_KEY"),
        "Accept: application/json",
    ],
]);
$body   = json_decode(curl_exec($ch), true);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);

if (!($body["success"] ?? false)) {
    throw new RuntimeException("$status {$body["error"]["code"]}: {$body["error"]["message"]}");
}
print_r($body["data"]);
JavaScript
JavaScript
const response = await fetch("https://YOUR_DOMAIN/api/company/v1/parcels/DS-000000", {
  method: "GET",
  headers: {
    "Authorization": `Bearer ${process.env.DS_COMPANY_API_KEY}`,
    "Accept": "application/json",
  },
});
const json = await response.json();
if (!json.success) {
  throw new Error(`${response.status} ${json.error.code}: ${json.error.message}`);
}
console.log(json.data);
Python
Python
import os
import requests

response = requests.get(
    "https://YOUR_DOMAIN/api/company/v1/parcels/DS-000000",
    headers={
        "Authorization": f"Bearer {os.environ['DS_COMPANY_API_KEY']}",
        "Accept": "application/json",
    },
    timeout=30,
)
body = response.json()
if not body["success"]:
    raise RuntimeError(f"{response.status_code} {body['error']['code']}: {body['error']['message']}")
print(body["data"])

Method POST /parcels/{tracking}/assign #

Assign a courier

Assigns a courier of your company. Assign parcels that are at the warehouse or hub (status Received Warehouse or Received by hub) or already assigned. A parcel already with that courier (status Delivery Man Assign or Delivery Re-Schedule) is returned unchanged. A parcel whose status has no transition to Delivery Man Assign, such as a pending one or one still in pickup, is refused by the workflow and the API answers 500 server_error: check status_code first. If your company requires couriers to be online and this one is offline you get 422 on the field delivery_man_id.

Path parameters

NameTypeRequiredDescription
tracking string required

A tracking number, or the reference the merchant sent (the newest match among the parcels of ALL your merchants wins, so two merchants that share a reference value can give you the other merchant's parcel: prefer the tracking number).

Example DS-000000

Request body

application/json · required

FieldType RequiredDescription
deliveryman_id integer required

The courier: an id from GET /deliverymen. It must be one of your couriers.

note string optional

Optional note recorded with the assignment.

force boolean optional

Send true to go beyond the courier's parcel limit.

Example

JSON
{
  "deliveryman_id": 5,
  "note": "Call before arriving",
  "force": false
}

Responses

200 The parcel after the assignment.

The parcel after the assignment.

Headers: X-RateLimit-Limit X-RateLimit-Remaining

FieldType RequiredDescription
success boolean required

constant true

data Parcel required

A parcel as returned by the API and carried by webhooks (same fields every time).

Example

JSON · 200
{
  "success": true,
  "data": {
    "id": 1043,
    "tracking_number": "DS-000000",
    "reference": "ORDER-1043",
    "stage": "out_for_delivery",
    "status_code": 7,
    "status_label": "Delivery Man Assign",
    "company_id": 3,
    "company_name": "Example Delivery",
    "external_tracking_number": null,
    "recipient": {
      "name": "Example Name",
      "phone": "0900000000",
      "governorate": "Damascus",
      "address": "Example street, building 1",
      "lat": null,
      "lng": null
    },
    "cod_amount": 25,
    "cod_collected": 0,
    "weight_kg": 1.5,
    "charges": {
      "delivery_fee": 6,
      "platform_fee": 1
    },
    "delivery_attempts": 0,
    "note": null,
    "tracking_url": "https://YOUR_DOMAIN/t/DS-000000",
    "created_at": "2026-01-01T10:00:00+00:00",
    "updated_at": "2026-01-01T12:10:00+00:00",
    "merchant": {
      "id": 41,
      "name": "Example Shop"
    },
    "deliveryman": {
      "id": 5,
      "name": "Example Courier"
    }
  }
}

401 Missing or unknown API key. common response, see below

402 The company subscription has ended (subscription_expired). common response, see below

403 Access refused. common response, see below

404 not_found:

not_found: no parcel with this number at your company.

Headers: X-RateLimit-Limit X-RateLimit-Remaining

Example — Unknown parcel

JSON · 404
{
  "success": false,
  "error": {
    "code": "not_found",
    "message": "No parcel with this number in your company."
  }
}
409 deliveryman_full:

deliveryman_full: the courier already holds the maximum number of open parcels. Send force: true to go beyond the limit.

Headers: X-RateLimit-Limit X-RateLimit-Remaining

Example — Courier full

JSON · 409
{
  "success": false,
  "error": {
    "code": "deliveryman_full",
    "message": "The courier has reached the parcel limit (50 / 50 undelivered). Send force=true to bypass the limit."
  }
}
422 validation_failed:

validation_failed: deliveryman_id missing or not one of your couriers (only message, no fields), or the courier is offline (field delivery_man_id).

Headers: X-RateLimit-Limit X-RateLimit-Remaining

Example — Unknown courier

JSON · 422
{
  "success": false,
  "error": {
    "code": "validation_failed",
    "message": "deliveryman_id is required and must be a courier of your company (GET /deliverymen)."
  }
}

429 Rate limit. common response, see below

500 Unexpected server error. common response, see below

Code samples

curl
curl
curl -X POST "https://YOUR_DOMAIN/api/company/v1/parcels/DS-000000/assign" \
  -H "Authorization: Bearer YOUR_COMPANY_API_KEY" \
  -H "Accept: application/json" \
  -H "Content-Type: application/json" \
  -d '{
  "deliveryman_id": 5,
  "note": "Call before arriving",
  "force": false
}'
PHP
PHP
<?php
$ch = curl_init("https://YOUR_DOMAIN/api/company/v1/parcels/DS-000000/assign");
curl_setopt_array($ch, [
    CURLOPT_CUSTOMREQUEST  => "POST",
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_TIMEOUT        => 30,
    CURLOPT_HTTPHEADER     => [
        "Authorization: Bearer " . getenv("DS_COMPANY_API_KEY"),
        "Accept: application/json",
        "Content-Type: application/json",
    ],
    CURLOPT_POSTFIELDS     => json_encode([
        'deliveryman_id' => 5,
        'note' => 'Call before arriving',
        'force' => false,
    ]),
]);
$body   = json_decode(curl_exec($ch), true);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);

if (!($body["success"] ?? false)) {
    throw new RuntimeException("$status {$body["error"]["code"]}: {$body["error"]["message"]}");
}
print_r($body["data"]);
JavaScript
JavaScript
const response = await fetch("https://YOUR_DOMAIN/api/company/v1/parcels/DS-000000/assign", {
  method: "POST",
  headers: {
    "Authorization": `Bearer ${process.env.DS_COMPANY_API_KEY}`,
    "Accept": "application/json",
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    "deliveryman_id": 5,
    "note": "Call before arriving",
    "force": false
  }),
});
const json = await response.json();
if (!json.success) {
  throw new Error(`${response.status} ${json.error.code}: ${json.error.message}`);
}
console.log(json.data);
Python
Python
import os
import requests

response = requests.post(
    "https://YOUR_DOMAIN/api/company/v1/parcels/DS-000000/assign",
    headers={
        "Authorization": f"Bearer {os.environ['DS_COMPANY_API_KEY']}",
        "Accept": "application/json",
    },
    json={
        "deliveryman_id": 5,
        "note": "Call before arriving",
        "force": False,
    },
    timeout=30,
)
body = response.json()
if not body["success"]:
    raise RuntimeError(f"{response.status_code} {body['error']['code']}: {body['error']['message']}")
print(body["data"])

Method POST /parcels/{tracking}/status #

Update the status of a parcel

Applies a status change through the parcel workflow: received, deliver, partial, fail, reschedule or returned. Parcels sent to an external shipping carrier cannot be changed here (409 not_allowed). A change the workflow does not allow, or a collected above the COD amount, gives 409 invalid_transition with the reason in the message. Mark a parcel delivered only after assigning a courier.

Path parameters

NameTypeRequiredDescription
tracking string required

A tracking number, or the reference the merchant sent (the newest match among the parcels of ALL your merchants wins, so two merchants that share a reference value can give you the other merchant's parcel: prefer the tracking number).

Example DS-000000

Request body

application/json · required

FieldType RequiredDescription
action string required

The change to apply: received (Received Warehouse), deliver (Delivered), partial (partly delivered; collected is required), fail (Return to Courier, stage returning), reschedule (back to the warehouse for a new dispatch, counts one attempt) or returned (return received by the merchant, final).

One of: received deliver partial fail reschedule returned

collected number optional

Amount collected, in US dollars. Required for partial; it cannot exceed the COD amount.

minimum 0

note string optional

Optional note recorded with the change.

Example

JSON
{
  "action": "partial",
  "collected": 15,
  "note": "One item returned"
}

Responses

200 The parcel after the change.

The parcel after the change.

Headers: X-RateLimit-Limit X-RateLimit-Remaining

FieldType RequiredDescription
success boolean required

constant true

data Parcel required

A parcel as returned by the API and carried by webhooks (same fields every time).

Example

JSON · 200
{
  "success": true,
  "data": {
    "id": 1043,
    "tracking_number": "DS-000000",
    "reference": "ORDER-1043",
    "stage": "delivered",
    "status_code": 9,
    "status_label": "Delivered",
    "company_id": 3,
    "company_name": "Example Delivery",
    "external_tracking_number": null,
    "recipient": {
      "name": "Example Name",
      "phone": "0900000000",
      "governorate": "Damascus",
      "address": "Example street, building 1",
      "lat": null,
      "lng": null
    },
    "cod_amount": 25,
    "cod_collected": 25,
    "weight_kg": 1.5,
    "charges": {
      "delivery_fee": 6,
      "platform_fee": 1
    },
    "delivery_attempts": 0,
    "note": null,
    "tracking_url": "https://YOUR_DOMAIN/t/DS-000000",
    "created_at": "2026-01-01T10:00:00+00:00",
    "updated_at": "2026-01-01T12:10:00+00:00",
    "merchant": {
      "id": 41,
      "name": "Example Shop"
    },
    "deliveryman": {
      "id": 5,
      "name": "Example Courier"
    }
  }
}

401 Missing or unknown API key. common response, see below

402 The company subscription has ended (subscription_expired). common response, see below

403 Access refused. common response, see below

404 not_found:

not_found: no parcel with this number at your company.

Headers: X-RateLimit-Limit X-RateLimit-Remaining

Example — Unknown parcel

JSON · 404
{
  "success": false,
  "error": {
    "code": "not_found",
    "message": "No parcel with this number in your company."
  }
}
409 not_allowed:

not_allowed: the parcel is with an external carrier, which controls its status. invalid_transition: the action does not fit the current status, or collected is too high.

Headers: X-RateLimit-Limit X-RateLimit-Remaining

Example — External carrier

JSON · 409
{
  "success": false,
  "error": {
    "code": "not_allowed",
    "message": "This parcel is with an external shipping company — its status updates from them automatically."
  }
}

Example — Wrong status

JSON · 409
{
  "success": false,
  "error": {
    "code": "invalid_transition",
    "message": "Localized text that names the action, the current status and the reason."
  }
}
422 validation_failed:

validation_failed: unknown action (message only), or partial without a numeric collected (field collected).

Headers: X-RateLimit-Limit X-RateLimit-Remaining

Example — Missing collected amount

JSON · 422
{
  "success": false,
  "error": {
    "code": "validation_failed",
    "message": "Partial delivery requires collected (the amount collected).",
    "fields": {
      "collected": [
        "Partial delivery requires collected (the amount collected)."
      ]
    }
  }
}

429 Rate limit. common response, see below

500 Unexpected server error. common response, see below

Code samples

curl
curl
curl -X POST "https://YOUR_DOMAIN/api/company/v1/parcels/DS-000000/status" \
  -H "Authorization: Bearer YOUR_COMPANY_API_KEY" \
  -H "Accept: application/json" \
  -H "Content-Type: application/json" \
  -d '{
  "action": "partial",
  "collected": 15,
  "note": "One item returned"
}'
PHP
PHP
<?php
$ch = curl_init("https://YOUR_DOMAIN/api/company/v1/parcels/DS-000000/status");
curl_setopt_array($ch, [
    CURLOPT_CUSTOMREQUEST  => "POST",
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_TIMEOUT        => 30,
    CURLOPT_HTTPHEADER     => [
        "Authorization: Bearer " . getenv("DS_COMPANY_API_KEY"),
        "Accept: application/json",
        "Content-Type: application/json",
    ],
    CURLOPT_POSTFIELDS     => json_encode([
        'action' => 'partial',
        'collected' => 15,
        'note' => 'One item returned',
    ]),
]);
$body   = json_decode(curl_exec($ch), true);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);

if (!($body["success"] ?? false)) {
    throw new RuntimeException("$status {$body["error"]["code"]}: {$body["error"]["message"]}");
}
print_r($body["data"]);
JavaScript
JavaScript
const response = await fetch("https://YOUR_DOMAIN/api/company/v1/parcels/DS-000000/status", {
  method: "POST",
  headers: {
    "Authorization": `Bearer ${process.env.DS_COMPANY_API_KEY}`,
    "Accept": "application/json",
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    "action": "partial",
    "collected": 15,
    "note": "One item returned"
  }),
});
const json = await response.json();
if (!json.success) {
  throw new Error(`${response.status} ${json.error.code}: ${json.error.message}`);
}
console.log(json.data);
Python
Python
import os
import requests

response = requests.post(
    "https://YOUR_DOMAIN/api/company/v1/parcels/DS-000000/status",
    headers={
        "Authorization": f"Bearer {os.environ['DS_COMPANY_API_KEY']}",
        "Accept": "application/json",
    },
    json={
        "action": "partial",
        "collected": 15,
        "note": "One item returned",
    },
    timeout=30,
)
body = response.json()
if not body["success"]:
    raise RuntimeError(f"{response.status_code} {body['error']['code']}: {body['error']['message']}")
print(body["data"])

Objects

Objects that several endpoints and webhook events return. Each is described once here; the other tables link to it.

Meta #

Pagination block. There are no links and no cursor.

FieldType RequiredDescription
page integer required

Current page.

per_page integer required

Effective page size.

total integer required

Number of records after the filters.

last_page integer required

Number of pages (1 when there are no results).

Parcel #

A parcel as returned by the API and carried by webhooks (same fields every time).

FieldType RequiredDescription
id integer required

Parcel id.

tracking_number string required

Tracking number. Use it in {tracking} paths and on the public tracking page.

reference string | null required

Your own reference (order number) sent at creation.

stage string required

Simplified, stable state. Prefer it to status_code in your logic: awaiting_funds (saved, waiting for the wallet balance), pending (status 1), out_for_delivery (7, 8), delivered (9), partially_delivered (32), returning (11, 12, 24, 26, 27), returned (13, 30) and processing (every other status).

One of: pending processing out_for_delivery delivered partially_delivered returning returned awaiting_funds

status_code integer required

Exact internal status code (for example 1 Pending, 5 Received Warehouse, 7 Delivery Man Assign, 9 Delivered, 13 Returned Merchant, 32 Partial Delivered).

status_label string required

Label of the status in the request language.

company_id integer required

Delivery company or carrier that handles the parcel.

company_name string | null required

Name of that company.

external_tracking_number string | null required

Tracking number at the external shipping carrier, when the parcel was sent to one.

recipient object required

The recipient of the parcel.

name string | null required

Recipient name.

phone string | null required

Recipient phone.

governorate string | null required

Recipient governorate.

address string | null required

Recipient address.

lat number | null required

Latitude of the recipient.

lng number | null required

Longitude of the recipient.

cod_amount number required

Cash-on-delivery amount to collect, always in US dollars.

cod_collected number required

Amount collected so far, in US dollars (the collected amount for a partial delivery).

weight_kg number | null required

Parcel weight in kilograms.

charges object required

Fees of the parcel.

delivery_fee number required

Delivery fee in US dollars (the carrier cost for a carrier parcel).

platform_fee number required

Platform fee in US dollars.

delivery_attempts integer required

Number of delivery attempts.

note string | null required

Note sent with the parcel.

tracking_url string (uri) required

Public tracking page of the parcel.

created_at string (date-time) | null required

Creation time (ISO 8601 with a UTC offset).

updated_at string (date-time) | null required

Last update time (ISO 8601 with a UTC offset).

merchant object | null required

The merchant that owns the parcel.

id integer required

Merchant id.

name string | null required

Business name of the merchant.

deliveryman object | null required

The courier holding the parcel, or null when none does.

id integer required

Courier id.

name string | null required

Courier name.

Webhook events

The platform sends these messages to the HTTPS endpoints you register (outgoing webhooks). Every message is a signed JSON POST and is retried when your system does not answer with a 2xx status. Each event below shows its body; all of them share the same headers and signature.

Request headers

NameTypeDescription
X-DS-Eventstring

The event name (for example parcel.delivered, or ping for a test).

X-DS-Event-Idstring

Event id, evt_ plus 24 lowercase characters. The same for every attempt and every endpoint of the event: use it to ignore duplicates.

X-DS-Deliverystring (uuid)

UUID of this message to this endpoint. The same across attempts.

X-DS-Attemptinteger

Attempt number, starting at 1.

X-DS-Timestampstring

Send time in Unix seconds, renewed on every attempt. It is part of the signed text.

X-DS-Signaturestring

sha256= followed by the lowercase hex of HMAC-SHA256 with the endpoint's signing key (whsec_..., the whole string) over the text X-DS-Timestamp + "." + raw request body. Compare in constant time.

Event POST parcel.created #

Parcel created

A parcel was created for one of your merchants. A new parcel normally sends this and then parcel.status_changed (status 5). Sent to the webhook links you register in the company panel (Integrations and API, then Webhook), for the parcels of your company only; the data object is the company parcel object with merchant and deliveryman.

Message body

FieldType RequiredDescription
id string required

Event id (evt_ plus 24 lowercase characters); the same as the X-DS-Event-Id header.

pattern ^evt_[a-z0-9]{24}$

event string required

constant "parcel.created"

created_at string (date-time) required

Time the event was created (ISO 8601).

data Parcel required

A parcel as returned by the API and carried by webhooks (same fields every time).

Example

JSON
{
  "id": "evt_abcdefghijklmnopqrstuvwx",
  "event": "parcel.created",
  "created_at": "2026-01-01T12:10:00+00:00",
  "data": {
    "id": 1043,
    "tracking_number": "DS-000000",
    "reference": "ORDER-1043",
    "stage": "out_for_delivery",
    "status_code": 7,
    "status_label": "Delivery Man Assign",
    "company_id": 3,
    "company_name": "Example Delivery",
    "external_tracking_number": null,
    "recipient": {
      "name": "Example Name",
      "phone": "0900000000",
      "governorate": "Damascus",
      "address": "Example street, building 1",
      "lat": null,
      "lng": null
    },
    "cod_amount": 25,
    "cod_collected": 0,
    "weight_kg": 1.5,
    "charges": {
      "delivery_fee": 6,
      "platform_fee": 1
    },
    "delivery_attempts": 0,
    "note": null,
    "tracking_url": "https://YOUR_DOMAIN/t/DS-000000",
    "created_at": "2026-01-01T10:00:00+00:00",
    "updated_at": "2026-01-01T12:10:00+00:00",
    "merchant": {
      "id": 41,
      "name": "Example Shop"
    },
    "deliveryman": {
      "id": 5,
      "name": "Example Courier"
    }
  }
}

Event POST parcel.status_changed #

Parcel status changed

Any status change made through the parcel workflow. One change can send this and a stage event, each with its own event id. Not sent by the dedicated staff pickup and hub forms or by some undo forms: read the parcel when you need its current state. Sent to the webhook links you register in the company panel (Integrations and API, then Webhook), for the parcels of your company only; the data object is the company parcel object with merchant and deliveryman.

Message body

FieldType RequiredDescription
id string required

Event id (evt_ plus 24 lowercase characters); the same as the X-DS-Event-Id header.

pattern ^evt_[a-z0-9]{24}$

event string required

constant "parcel.status_changed"

created_at string (date-time) required

Time the event was created (ISO 8601).

data Parcel required

A parcel as returned by the API and carried by webhooks (same fields every time).

Example

JSON
{
  "id": "evt_abcdefghijklmnopqrstuvwx",
  "event": "parcel.status_changed",
  "created_at": "2026-01-01T12:10:00+00:00",
  "data": {
    "id": 1043,
    "tracking_number": "DS-000000",
    "reference": "ORDER-1043",
    "stage": "out_for_delivery",
    "status_code": 7,
    "status_label": "Delivery Man Assign",
    "company_id": 3,
    "company_name": "Example Delivery",
    "external_tracking_number": null,
    "recipient": {
      "name": "Example Name",
      "phone": "0900000000",
      "governorate": "Damascus",
      "address": "Example street, building 1",
      "lat": null,
      "lng": null
    },
    "cod_amount": 25,
    "cod_collected": 0,
    "weight_kg": 1.5,
    "charges": {
      "delivery_fee": 6,
      "platform_fee": 1
    },
    "delivery_attempts": 0,
    "note": null,
    "tracking_url": "https://YOUR_DOMAIN/t/DS-000000",
    "created_at": "2026-01-01T10:00:00+00:00",
    "updated_at": "2026-01-01T12:10:00+00:00",
    "merchant": {
      "id": 41,
      "name": "Example Shop"
    },
    "deliveryman": {
      "id": 5,
      "name": "Example Courier"
    }
  }
}

Event POST parcel.out_for_delivery #

Out for delivery

The parcel is with a courier (statuses 7 and 8). Sent to the webhook links you register in the company panel (Integrations and API, then Webhook), for the parcels of your company only; the data object is the company parcel object with merchant and deliveryman.

Message body

FieldType RequiredDescription
id string required

Event id (evt_ plus 24 lowercase characters); the same as the X-DS-Event-Id header.

pattern ^evt_[a-z0-9]{24}$

event string required

constant "parcel.out_for_delivery"

created_at string (date-time) required

Time the event was created (ISO 8601).

data Parcel required

A parcel as returned by the API and carried by webhooks (same fields every time).

Example

JSON
{
  "id": "evt_abcdefghijklmnopqrstuvwx",
  "event": "parcel.out_for_delivery",
  "created_at": "2026-01-01T12:10:00+00:00",
  "data": {
    "id": 1043,
    "tracking_number": "DS-000000",
    "reference": "ORDER-1043",
    "stage": "out_for_delivery",
    "status_code": 7,
    "status_label": "Delivery Man Assign",
    "company_id": 3,
    "company_name": "Example Delivery",
    "external_tracking_number": null,
    "recipient": {
      "name": "Example Name",
      "phone": "0900000000",
      "governorate": "Damascus",
      "address": "Example street, building 1",
      "lat": null,
      "lng": null
    },
    "cod_amount": 25,
    "cod_collected": 0,
    "weight_kg": 1.5,
    "charges": {
      "delivery_fee": 6,
      "platform_fee": 1
    },
    "delivery_attempts": 0,
    "note": null,
    "tracking_url": "https://YOUR_DOMAIN/t/DS-000000",
    "created_at": "2026-01-01T10:00:00+00:00",
    "updated_at": "2026-01-01T12:10:00+00:00",
    "merchant": {
      "id": 41,
      "name": "Example Shop"
    },
    "deliveryman": {
      "id": 5,
      "name": "Example Courier"
    }
  }
}

Event POST parcel.delivered #

Delivered

The parcel was delivered, fully or partially (statuses 9 and 32). data.cod_collected has the collected amount. Sent to the webhook links you register in the company panel (Integrations and API, then Webhook), for the parcels of your company only; the data object is the company parcel object with merchant and deliveryman.

Message body

FieldType RequiredDescription
id string required

Event id (evt_ plus 24 lowercase characters); the same as the X-DS-Event-Id header.

pattern ^evt_[a-z0-9]{24}$

event string required

constant "parcel.delivered"

created_at string (date-time) required

Time the event was created (ISO 8601).

data Parcel required

A parcel as returned by the API and carried by webhooks (same fields every time).

Example

JSON
{
  "id": "evt_abcdefghijklmnopqrstuvwx",
  "event": "parcel.delivered",
  "created_at": "2026-01-01T12:10:00+00:00",
  "data": {
    "id": 1043,
    "tracking_number": "DS-000000",
    "reference": "ORDER-1043",
    "stage": "out_for_delivery",
    "status_code": 7,
    "status_label": "Delivery Man Assign",
    "company_id": 3,
    "company_name": "Example Delivery",
    "external_tracking_number": null,
    "recipient": {
      "name": "Example Name",
      "phone": "0900000000",
      "governorate": "Damascus",
      "address": "Example street, building 1",
      "lat": null,
      "lng": null
    },
    "cod_amount": 25,
    "cod_collected": 0,
    "weight_kg": 1.5,
    "charges": {
      "delivery_fee": 6,
      "platform_fee": 1
    },
    "delivery_attempts": 0,
    "note": null,
    "tracking_url": "https://YOUR_DOMAIN/t/DS-000000",
    "created_at": "2026-01-01T10:00:00+00:00",
    "updated_at": "2026-01-01T12:10:00+00:00",
    "merchant": {
      "id": 41,
      "name": "Example Shop"
    },
    "deliveryman": {
      "id": 5,
      "name": "Example Courier"
    }
  }
}

Event POST parcel.returning #

Returning

Delivery failed and the parcel is on its way back. Sent to the webhook links you register in the company panel (Integrations and API, then Webhook), for the parcels of your company only; the data object is the company parcel object with merchant and deliveryman.

Message body

FieldType RequiredDescription
id string required

Event id (evt_ plus 24 lowercase characters); the same as the X-DS-Event-Id header.

pattern ^evt_[a-z0-9]{24}$

event string required

constant "parcel.returning"

created_at string (date-time) required

Time the event was created (ISO 8601).

data Parcel required

A parcel as returned by the API and carried by webhooks (same fields every time).

Example

JSON
{
  "id": "evt_abcdefghijklmnopqrstuvwx",
  "event": "parcel.returning",
  "created_at": "2026-01-01T12:10:00+00:00",
  "data": {
    "id": 1043,
    "tracking_number": "DS-000000",
    "reference": "ORDER-1043",
    "stage": "out_for_delivery",
    "status_code": 7,
    "status_label": "Delivery Man Assign",
    "company_id": 3,
    "company_name": "Example Delivery",
    "external_tracking_number": null,
    "recipient": {
      "name": "Example Name",
      "phone": "0900000000",
      "governorate": "Damascus",
      "address": "Example street, building 1",
      "lat": null,
      "lng": null
    },
    "cod_amount": 25,
    "cod_collected": 0,
    "weight_kg": 1.5,
    "charges": {
      "delivery_fee": 6,
      "platform_fee": 1
    },
    "delivery_attempts": 0,
    "note": null,
    "tracking_url": "https://YOUR_DOMAIN/t/DS-000000",
    "created_at": "2026-01-01T10:00:00+00:00",
    "updated_at": "2026-01-01T12:10:00+00:00",
    "merchant": {
      "id": 41,
      "name": "Example Shop"
    },
    "deliveryman": {
      "id": 5,
      "name": "Example Courier"
    }
  }
}

Event POST parcel.returned #

Returned to the merchant

The merchant received the returned parcel (statuses 30 and 13). Sent to the webhook links you register in the company panel (Integrations and API, then Webhook), for the parcels of your company only; the data object is the company parcel object with merchant and deliveryman.

Message body

FieldType RequiredDescription
id string required

Event id (evt_ plus 24 lowercase characters); the same as the X-DS-Event-Id header.

pattern ^evt_[a-z0-9]{24}$

event string required

constant "parcel.returned"

created_at string (date-time) required

Time the event was created (ISO 8601).

data Parcel required

A parcel as returned by the API and carried by webhooks (same fields every time).

Example

JSON
{
  "id": "evt_abcdefghijklmnopqrstuvwx",
  "event": "parcel.returned",
  "created_at": "2026-01-01T12:10:00+00:00",
  "data": {
    "id": 1043,
    "tracking_number": "DS-000000",
    "reference": "ORDER-1043",
    "stage": "out_for_delivery",
    "status_code": 7,
    "status_label": "Delivery Man Assign",
    "company_id": 3,
    "company_name": "Example Delivery",
    "external_tracking_number": null,
    "recipient": {
      "name": "Example Name",
      "phone": "0900000000",
      "governorate": "Damascus",
      "address": "Example street, building 1",
      "lat": null,
      "lng": null
    },
    "cod_amount": 25,
    "cod_collected": 0,
    "weight_kg": 1.5,
    "charges": {
      "delivery_fee": 6,
      "platform_fee": 1
    },
    "delivery_attempts": 0,
    "note": null,
    "tracking_url": "https://YOUR_DOMAIN/t/DS-000000",
    "created_at": "2026-01-01T10:00:00+00:00",
    "updated_at": "2026-01-01T12:10:00+00:00",
    "merchant": {
      "id": 41,
      "name": "Example Shop"
    },
    "deliveryman": {
      "id": 5,
      "name": "Example Courier"
    }
  }
}

Event POST parcel.cancelled #

Cancelled

The connected external carrier reported the parcel as cancelled. Sent to the webhook links you register in the company panel (Integrations and API, then Webhook), for the parcels of your company only; the data object is the company parcel object with merchant and deliveryman.

Message body

FieldType RequiredDescription
id string required

Event id (evt_ plus 24 lowercase characters); the same as the X-DS-Event-Id header.

pattern ^evt_[a-z0-9]{24}$

event string required

constant "parcel.cancelled"

created_at string (date-time) required

Time the event was created (ISO 8601).

data Parcel required

A parcel as returned by the API and carried by webhooks (same fields every time).

Example

JSON
{
  "id": "evt_abcdefghijklmnopqrstuvwx",
  "event": "parcel.cancelled",
  "created_at": "2026-01-01T12:10:00+00:00",
  "data": {
    "id": 1043,
    "tracking_number": "DS-000000",
    "reference": "ORDER-1043",
    "stage": "out_for_delivery",
    "status_code": 7,
    "status_label": "Delivery Man Assign",
    "company_id": 3,
    "company_name": "Example Delivery",
    "external_tracking_number": null,
    "recipient": {
      "name": "Example Name",
      "phone": "0900000000",
      "governorate": "Damascus",
      "address": "Example street, building 1",
      "lat": null,
      "lng": null
    },
    "cod_amount": 25,
    "cod_collected": 0,
    "weight_kg": 1.5,
    "charges": {
      "delivery_fee": 6,
      "platform_fee": 1
    },
    "delivery_attempts": 0,
    "note": null,
    "tracking_url": "https://YOUR_DOMAIN/t/DS-000000",
    "created_at": "2026-01-01T10:00:00+00:00",
    "updated_at": "2026-01-01T12:10:00+00:00",
    "merchant": {
      "id": 41,
      "name": "Example Shop"
    },
    "deliveryman": {
      "id": 5,
      "name": "Example Courier"
    }
  }
}

Event POST parcel.awaiting_funds #

Awaiting funds

The order was saved but the merchant's balance is too low. Sent to the webhook links you register in the company panel (Integrations and API, then Webhook), for the parcels of your company only; the data object is the company parcel object with merchant and deliveryman.

Message body

FieldType RequiredDescription
id string required

Event id (evt_ plus 24 lowercase characters); the same as the X-DS-Event-Id header.

pattern ^evt_[a-z0-9]{24}$

event string required

constant "parcel.awaiting_funds"

created_at string (date-time) required

Time the event was created (ISO 8601).

data Parcel required

A parcel as returned by the API and carried by webhooks (same fields every time).

Example

JSON
{
  "id": "evt_abcdefghijklmnopqrstuvwx",
  "event": "parcel.awaiting_funds",
  "created_at": "2026-01-01T12:10:00+00:00",
  "data": {
    "id": 1043,
    "tracking_number": "DS-000000",
    "reference": "ORDER-1043",
    "stage": "out_for_delivery",
    "status_code": 7,
    "status_label": "Delivery Man Assign",
    "company_id": 3,
    "company_name": "Example Delivery",
    "external_tracking_number": null,
    "recipient": {
      "name": "Example Name",
      "phone": "0900000000",
      "governorate": "Damascus",
      "address": "Example street, building 1",
      "lat": null,
      "lng": null
    },
    "cod_amount": 25,
    "cod_collected": 0,
    "weight_kg": 1.5,
    "charges": {
      "delivery_fee": 6,
      "platform_fee": 1
    },
    "delivery_attempts": 0,
    "note": null,
    "tracking_url": "https://YOUR_DOMAIN/t/DS-000000",
    "created_at": "2026-01-01T10:00:00+00:00",
    "updated_at": "2026-01-01T12:10:00+00:00",
    "merchant": {
      "id": 41,
      "name": "Example Shop"
    },
    "deliveryman": {
      "id": 5,
      "name": "Example Courier"
    }
  }
}

Errors

Every error raised by the API itself uses the same JSON envelope. Build your logic on error.code and the HTTP status, never on the message text. These are the codes this API can return; errors that the framework raises before the API code runs (IP and route rate limits, unknown paths, wrong methods) use a plain {"message": "..."} format instead.

Common responses

These answers can come from any endpoint of this API. Each endpoint lists only its own specific answers and links here for these.

401 Missing or unknown API key.

Missing or unknown API key. unauthenticated: no key in an Authorization: Bearer header. invalid_key: the key is unknown (renewed, revoked, mistyped, or of another type: only the company key ck_... works here).

Headers: X-RateLimit-Limit X-RateLimit-Remaining

Example — No key

JSON · 401
{
  "success": false,
  "error": {
    "code": "unauthenticated",
    "message": "Send the key in the header: Authorization: Bearer <API key>"
  }
}

Example — Unknown key

JSON · 401
{
  "success": false,
  "error": {
    "code": "invalid_key",
    "message": "The key is invalid or revoked. Generate a new key from the company dashboard ← Integrations and API."
  }
}
402 The company subscription has ended (subscription_expired).

The company subscription has ended (subscription_expired). Platform-owned and merchant-owned companies are exempt from the subscription check.

Headers: X-RateLimit-Limit X-RateLimit-Remaining

Example — Subscription ended

JSON · 402
{
  "success": false,
  "error": {
    "code": "subscription_expired",
    "message": "Company subscription expired — renew the subscription from the company dashboard."
  }
}
403 Access refused.

Access refused. account_inactive: the key's user or the company is suspended or missing. plan_feature: the company plan does not include the API feature.

Headers: X-RateLimit-Limit X-RateLimit-Remaining

Example — Suspended company

JSON · 403
{
  "success": false,
  "error": {
    "code": "account_inactive",
    "message": "Company account suspended."
  }
}

Example — Plan without the API

JSON · 403
{
  "success": false,
  "error": {
    "code": "plan_feature",
    "message": "Localized text that names the feature and the plan problem."
  }
}
429 Rate limit.

Rate limit. Two different answers share this status. (1) Plan limit (per minute or per day): the JSON error envelope with error.code rate_limited, error.retry_after and a Retry-After header. (2) IP or route limit: the framework answers before the API code runs, with {"message": "Too Many Attempts."} (JSON only when you send Accept: application/json; otherwise an HTML page) and the headers Retry-After, X-RateLimit-Limit, X-RateLimit-Remaining and X-RateLimit-Reset.

Headers: Retry-After X-RateLimit-Limit X-RateLimit-Remaining X-RateLimit-Reset

Example — Plan limit

JSON · 429
{
  "success": false,
  "error": {
    "code": "rate_limited",
    "message": "Localized text with the limit and the wait time",
    "retry_after": 42
  }
}

Example — IP or route limit

JSON · 429
{
  "message": "Too Many Attempts."
}
500 Unexpected server error.

Unexpected server error. The message quotes a short reference that support can find in the logs; repeat the request only if its outcome is unknown.

Headers: X-RateLimit-Limit X-RateLimit-Remaining

Example — Server error

JSON · 500
{
  "success": false,
  "error": {
    "code": "server_error",
    "message": "An error occurred on our side. If it happens again, contact support and mention reference E000000.",
    "reference": "E000000"
  }
}

Error codes

CodeHTTPDescription
invalid_key401

The key is unknown (renewed, revoked, mistyped, or of another type: only the company key ck_... works here).

unauthenticated401

No key in an Authorization: Bearer header.

insufficient_balance402

The balance is not enough for the parcel fee.

subscription_expired402

Your company subscription has ended.

account_inactive403

The key's user or the company is suspended or missing.

plan_feature403

The company plan does not include the API feature.

not_found404

No parcel with this tracking number or reference at your company.

deliveryman_full409

The courier already holds the maximum number of open parcels. Send force: true to go beyond the limit.

duplicate_reference409

The merchant already has a parcel with this reference. Fetch it with GET /parcels?merchant_id=<id>&reference=<reference> (a bare GET /parcels/{reference} searches all your merchants).

invalid_transition409

The action does not fit the current status, or collected is too high.

not_allowed409

The parcel is with an external carrier, which controls its status.

carrier_error422

The carrier could not process the parcel.

validation_failed422

Unknown stage. The message lists the available values; there is no fields object.

rate_limited429
server_error500

More resources