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.
https://deliverysyria.com/docs/api/company-v1.jsonOverview
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/v1ReplaceYOUR_DOMAINwith your platform address.- Authentication
The API key of your delivery company in the
Authorizationheader: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 givesinvalid_key. WooCommerceck_...consumer keys are a different thing and never work here.Authorization: Bearer YOUR_COMPANY_API_KEY- Accept-Language
Language of error messages and labels (status labels, event notes, names). If the value starts with
enthe 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
/apirequest. (2) Route limits, counted per IP address on ONE shared counter: the group limit of 300 per minute, and 120 forPOST /parcels; because aPOST /parcelsrequest 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_limitedwithRetry-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
| Field | Type | Required | Description |
|---|---|---|---|
success |
boolean |
required |
constant |
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 ( |
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
{
"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
Code samples
curl
curl -X GET "https://YOUR_DOMAIN/api/company/v1/me" \
-H "Authorization: Bearer YOUR_COMPANY_API_KEY" \
-H "Accept: application/json"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
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
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
| Name | Type | Required | Description |
|---|---|---|---|
q |
string |
optional |
Part of the business name or of the mobile number. 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 minimum 1maximum 100default |
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 |
Responses
200 A page of merchants.
A page of merchants.
Headers: X-RateLimit-Limit X-RateLimit-Remaining
| Field | Type | Required | Description |
|---|---|---|---|
success |
boolean |
required |
constant |
data |
array<object> |
required | |
id |
integer |
required |
Send it as |
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 |
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 One of: |
default |
boolean |
required |
True for the merchant's default location. |
meta |
Meta |
required |
Pagination block. There are no links and no cursor. |
Example
{
"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
Code samples
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
$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
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
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.
Responses
200 Your couriers.
Your couriers.
Headers: X-RateLimit-Limit X-RateLimit-Remaining
| Field | Type | Required | Description |
|---|---|---|---|
success |
boolean |
required |
constant |
data |
array<object> |
required | |
id |
integer |
required |
Send it as |
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
{
"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
Code samples
curl
curl -X GET "https://YOUR_DOMAIN/api/company/v1/deliverymen" \
-H "Authorization: Bearer YOUR_COMPANY_API_KEY" \
-H "Accept: application/json"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
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
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
| Name | Type | Required | Description |
|---|---|---|---|
merchant_id |
integer |
optional |
Only the parcels of this merchant. Example |
deliveryman_id |
integer |
optional |
Only the parcels held by this courier. Example |
reference |
string |
optional |
Exact match on the merchant's reference. Example |
from |
string (date) |
optional |
First creation day, Example |
to |
string (date) |
optional |
Last creation day, Example |
updated_since |
string |
optional |
Only parcels updated at or after this time, Example |
stage |
string |
optional |
Filter by stage. Unlike the merchant API an unknown value is rejected with One of: Example |
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 minimum 1maximum 100default |
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 |
Responses
200 A page of parcels.
A page of parcels.
Headers: X-RateLimit-Limit X-RateLimit-Remaining
| Field | Type | Required | Description |
|---|---|---|---|
success |
boolean |
required |
constant |
data |
array<Parcel> |
required | |
meta |
Meta |
required |
Pagination block. There are no links and no cursor. |
Example
{
"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
{
"success": false,
"error": {
"code": "validation_failed",
"message": "stage is unknown. Available: pending، processing، out_for_delivery، delivered، partially_delivered، returning، returned"
}
}Code samples
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
$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
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
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
| Field | Type | Required | Description |
|---|---|---|---|
merchant_id |
integer |
required |
The merchant the parcel is created for: an |
recipient |
object |
required |
The recipient. Each field may instead be sent flat at the top level ( |
name |
string |
required |
Recipient name. max length 191 |
phone |
string |
required |
Recipient phone number: digits, spaces, max length 30pattern |
address |
string |
required |
Recipient address. max length 1000 |
governorate |
string |
optional |
Governorate name (English max length 120also accepted as |
lat |
number |
optional |
Recipient latitude. Required for distance pricing ( minimum -90maximum 90 |
lng |
number |
optional |
Recipient longitude. Required for distance pricing ( minimum -180maximum 180also accepted as |
reference |
string |
optional |
The merchant's own reference (order number), up to 191 characters. A reference the merchant already used gives max length 191also accepted as |
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 |
currency |
string |
optional |
Currency code of One of: default |
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 |
pickup_location_id |
integer |
optional |
One of the merchant's pickup locations ( also accepted as |
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: One of: |
Example
{
"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
| Field | Type | Required | Description |
|---|---|---|---|
success |
boolean |
required |
constant |
data |
Parcel |
required |
A parcel as returned by the API and carried by webhooks (same fields every time). |
Example
{
"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
{
"success": false,
"error": {
"code": "insufficient_balance",
"message": "Insufficient wallet balance. Required: 7.00, available: 2.00"
}
}Example — Subscription ended
{
"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
{
"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
{
"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
{
"success": false,
"error": {
"code": "carrier_error",
"message": "Localized message from the carrier."
}
}Code samples
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
$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
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
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.
Path parameters
| Name | Type | Required | Description |
|---|---|---|---|
tracking |
string |
required |
A tracking number, or the Example |
Responses
200 The parcel with its events.
The parcel with its events.
Headers: X-RateLimit-Limit X-RateLimit-Remaining
| Field | Type | Required | Description |
|---|---|---|---|
success |
boolean |
required |
constant |
data |
object |
required |
The parcel object plus its history ( |
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: |
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
{
"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
{
"success": false,
"error": {
"code": "not_found",
"message": "No parcel with this number in your company."
}
}Code samples
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
$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
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
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
| Name | Type | Required | Description |
|---|---|---|---|
tracking |
string |
required |
A tracking number, or the Example |
Request body
application/json · required
| Field | Type | Required | Description |
|---|---|---|---|
deliveryman_id |
integer |
required |
The courier: an |
note |
string |
optional |
Optional note recorded with the assignment. |
force |
boolean |
optional |
Send |
Example
{
"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
| Field | Type | Required | Description |
|---|---|---|---|
success |
boolean |
required |
constant |
data |
Parcel |
required |
A parcel as returned by the API and carried by webhooks (same fields every time). |
Example
{
"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
{
"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
{
"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
{
"success": false,
"error": {
"code": "validation_failed",
"message": "deliveryman_id is required and must be a courier of your company (GET /deliverymen)."
}
}Code samples
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
$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
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
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
| Name | Type | Required | Description |
|---|---|---|---|
tracking |
string |
required |
A tracking number, or the Example |
Request body
application/json · required
| Field | Type | Required | Description |
|---|---|---|---|
action |
string |
required |
The change to apply: One of: |
collected |
number |
optional |
Amount collected, in US dollars. Required for minimum 0 |
note |
string |
optional |
Optional note recorded with the change. |
Example
{
"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
| Field | Type | Required | Description |
|---|---|---|---|
success |
boolean |
required |
constant |
data |
Parcel |
required |
A parcel as returned by the API and carried by webhooks (same fields every time). |
Example
{
"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
{
"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
{
"success": false,
"error": {
"code": "not_allowed",
"message": "This parcel is with an external shipping company — its status updates from them automatically."
}
}Example — Wrong status
{
"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
{
"success": false,
"error": {
"code": "validation_failed",
"message": "Partial delivery requires collected (the amount collected).",
"fields": {
"collected": [
"Partial delivery requires collected (the amount collected)."
]
}
}
}Code samples
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
$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
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
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.
| Field | Type | Required | Description |
|---|---|---|---|
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).
| Field | Type | Required | Description |
|---|---|---|---|
id |
integer |
required |
Parcel id. |
tracking_number |
string |
required |
Tracking number. Use it in |
reference |
string | null |
required |
Your own reference (order number) sent at creation. |
stage |
string |
required |
Simplified, stable state. Prefer it to One of: |
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
| Name | Type | Description |
|---|---|---|
X-DS-Event | string | The event name (for example |
X-DS-Event-Id | string | Event id, |
X-DS-Delivery | string (uuid) | UUID of this message to this endpoint. The same across attempts. |
X-DS-Attempt | integer | Attempt number, starting at 1. |
X-DS-Timestamp | string | Send time in Unix seconds, renewed on every attempt. It is part of the signed text. |
X-DS-Signature | string |
|
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
| Field | Type | Required | Description |
|---|---|---|---|
id |
string |
required |
Event id ( pattern |
event |
string |
required |
constant |
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
{
"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
| Field | Type | Required | Description |
|---|---|---|---|
id |
string |
required |
Event id ( pattern |
event |
string |
required |
constant |
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
{
"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
| Field | Type | Required | Description |
|---|---|---|---|
id |
string |
required |
Event id ( pattern |
event |
string |
required |
constant |
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
{
"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
| Field | Type | Required | Description |
|---|---|---|---|
id |
string |
required |
Event id ( pattern |
event |
string |
required |
constant |
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
{
"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
| Field | Type | Required | Description |
|---|---|---|---|
id |
string |
required |
Event id ( pattern |
event |
string |
required |
constant |
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
{
"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
| Field | Type | Required | Description |
|---|---|---|---|
id |
string |
required |
Event id ( pattern |
event |
string |
required |
constant |
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
{
"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
| Field | Type | Required | Description |
|---|---|---|---|
id |
string |
required |
Event id ( pattern |
event |
string |
required |
constant |
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
{
"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
| Field | Type | Required | Description |
|---|---|---|---|
id |
string |
required |
Event id ( pattern |
event |
string |
required |
constant |
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
{
"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
{
"success": false,
"error": {
"code": "unauthenticated",
"message": "Send the key in the header: Authorization: Bearer <API key>"
}
}Example — Unknown key
{
"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
{
"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
{
"success": false,
"error": {
"code": "account_inactive",
"message": "Company account suspended."
}
}Example — Plan without the API
{
"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
{
"success": false,
"error": {
"code": "rate_limited",
"message": "Localized text with the limit and the wait time",
"retry_after": 42
}
}Example — IP or route limit
{
"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
{
"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
| Code | HTTP | Description |
|---|---|---|
invalid_key | 401 | The key is unknown (renewed, revoked, mistyped, or of another type: only the company key |
unauthenticated | 401 | No key in an |
insufficient_balance | 402 | The balance is not enough for the parcel fee. |
subscription_expired | 402 | Your company subscription has ended. |
account_inactive | 403 | The key's user or the company is suspended or missing. |
plan_feature | 403 | The company plan does not include the API feature. |
not_found | 404 | No parcel with this tracking number or reference at your company. |
deliveryman_full | 409 | The courier already holds the maximum number of open parcels. Send |
duplicate_reference | 409 | The merchant already has a parcel with this |
invalid_transition | 409 | The action does not fit the current status, or |
not_allowed | 409 | The parcel is with an external carrier, which controls its status. |
carrier_error | 422 | The carrier could not process the parcel. |
validation_failed | 422 | Unknown |
rate_limited | 429 | |
server_error | 500 |