مرجع المطوّرون

مرجع واجهة التاجر API v1

كل نقاط واجهة التاجر (الإصدار v1): الطريقة والمسار والمعاملات مع قواعد التحقق وشكل الرد، مع أمثلة curl وJavaScript وPHP وPython لإنشاء طرد وعرضه وتتبّعه.

آخر تحديث 22 أكتوبر 2026 · آخر تحقق 22 أكتوبر 2026

الأساسيات#

تتيح واجهة التاجر لنظام التاجر في المنصة تسعير الطرود وإنشاءها وعرضها وتتبّعها وإلغاءها، وقراءة كشف الحساب، وإدارة روابط الويب هوك.

  • عنوان Base URL: نطاق المنصة مع /api/v1 (تعرضه اللوحة باسم Base URL).
  • المصادقة: Authorization: Bearer YOUR_API_KEY، راجع «المصادقة ومفاتيح الـ API». من يستخدمها: تجّار المنصة الذين تتضمن باقتهم ميزة الـ API.
  • الصيغة: JSON في الطلب والرد. أرسل Content-Type: application/json مع الطلبات التي تحمل جسماً، وأرسل دائماً Accept: application/json، وأضف Accept-Language: en لتصلك الرسائل والتسميات بالإنجليزية (بدونه تأتي بالعربية؛ وأمثلة هذه الصفحة معروضة بالإنجليزية).
  • الغلاف: {"success": true, "data": ...} (ولا يضيف meta إلا GET /parcels وGET /statement) و{"success": false, "error": {"code", "message"}}. راجع «نظرة عامة على واجهات الـ API» و«الأخطاء ورموز الحالة».

نقاط النهاية في لمحة#

الطريقة المسار الغرض حدّ المسار في الدقيقة
GET /me الحساب والباقة وأرصدة المحافظ 120
GET /companies شركات التوصيل وشركات الشحن المتاحة لك 120
GET /governorates المحافظات 120
GET /pickup-locations أماكن الاستلام لديك 120
GET /warehouse/stock بضاعتك في المستودعات 120
POST /quote تسعير طرد دون إنشائه 120
POST /parcels إنشاء طرد 60
GET /parcels عرض طرودك 120
GET /parcels/{tracking} طرد واحد مع أحداثه 120
POST /parcels/{tracking}/cancel إلغاء طرد واسترجاع الأجرة 120
GET /statement كشف المحفظة 30
GET /webhooks/events الأحداث التي يمكنك الاشتراك بها 120
GET /webhooks روابط الويب هوك لديك 120
POST /webhooks إضافة رابط ويب هوك 20
PATCH /webhooks/{id} تعديل رابط 120
DELETE /webhooks/{id} حذف رابط 120
POST /webhooks/{id}/test إرسال ping تجريبي 10

تُحتسب حدود المسارات لكل عنوان IP على عدّاد واحد مشترك: فطلب POST /parcels يُحسب على حدّ المجموعة (120) وعلى حدّه الخاص معاً، فليست هذه الأرقام حصصاً منفصلة. ولأن كل طلب من هذا النوع يُحسب مرتين، فإن المسار الذي له حدّ خاص لا يتيح من عنوان IP واحد إلا نحو نصفه: نحو 30 طلباً من POST /parcels و15 طلباً من GET /statement و10 طلبات من POST /webhooks و5 طلبات من POST /webhooks/{id}/test في الدقيقة، وأقل من ذلك إن أرسل العنوان نفسه طلبات أخرى في الدقيقة نفسها. ويُطبَّق أيضاً حدّ عام قدره 60 طلباً في الدقيقة لكل عنوان IP (راجع «نظرة عامة على واجهات الـ API»).

الحساب وقوائم الاختيار#

GET /me#

تُرجع بيانات حسابك، وهي أول استدعاء مناسب للتأكد من المفتاح.

json
{
  "success": true,
  "data": {
    "merchant_id": 12,
    "business_name": "My Shop",
    "plan": { "name": "Gold", "expires_on": "2026-12-01", "active": true },
    "balances": { "recharge": 150.5, "cod_due": 320, "held": 0, "company_due": 0 },
    "currency": "$"
  }
}

يكون plan قيمة null عندما لا يوجد سجل اشتراك. يعرض balances المحافظ recharge (رصيد الشحن) وcod_due (مستحقات التحصيل عند الاستلام) وheld (المبالغ المحجوزة) وcompany_due. الأرصدة بالدولار الأمريكي، أما currency فليس إلا رمز عملة المنصة المضبوط ($ افتراضياً)، بينما يُرجع GET /statement الرمز USD.

GET /companies#

تعرض شركات التوصيل وشركات الشحن التي يمكنك استخدامها. أرسل قيمة id على أنها company_id عند التسعير أو إنشاء الطرد.

json
{
  "success": true,
  "data": [
    { "id": 13, "name": "Example Delivery", "type": "delivery_company", "pricing_mode": "governorate", "supports_own_account": false, "coverage": null },
    { "id": 16, "name": "Example Carrier", "type": "carrier", "pricing_mode": "carrier_quote", "supports_own_account": true, "coverage": { "SY": [] } }
  ]
}
  • type هو delivery_company أو carrier.
  • pricing_mode هو governorate أو per_kg أو per_km أو km_kg، أو carrier_quote لشركات الشحن.
  • supports_own_account تكون true عندما تسمح شركة الشحن بحسابات التجّار الخاصة وتكون أنت قد ربطتَ حسابك بها (قائمة «حساباتي بشركات الشحن»). وإرسال use_own_account: true يفشل بالكود carrier_error عندما تسمح الشركة بحسابات التجّار الخاصة ولم تربط حسابك بعد. أما إذا كانت لا تسمح بها فيُتجاهل الخيار ويُستخدم حساب المنصة؛ وإذا كانت الشركة لا تعمل إلا بحسابك الخاص وأغفلت الخيار فتحصل أيضاً على carrier_error. ولا أثر لهذا الخيار على شركات التوصيل.
  • coverage تكون null عندما لا تقيّد الشركة التغطية، وإلا فهي كائن مثل {"SY": []} (كل المحافظات) أو {"SY": ["..."]} بأسماء المحافظات التي تغطيها.

GET /governorates#

تُرجع id وname وname_ar لكل محافظة فعّالة. أرسل name أو name_ar في recipient.governorate؛ وإن أرسلت id فأرسله نصاً ("3")، لأن الرقم في JSON مثل 3 يرفضه POST /parcels (انظر جدول الحقول).

GET /pickup-locations#

تُرجع id وname وphone وaddress وlat وlng وdefault وwarehouse_id (تكون null ما لم يكن المكان مستودعاً) وactive. عند عدم إرسال pickup_location_id يُستخدم مكانك الافتراضي. أضف الأماكن من اللوحة في «أماكن الاستلام».

GET /warehouse/stock#

لكل حجز مستودع فعّال: warehouse_id وwarehouse وpickup_location_id وpallets وitems (item_id وsku وname وqty وreserved وavailable). وتُرجع قائمة فارغة إن لم يكن لديك حجز فعّال في مستودع.

التسعير والطرود#

POST /quote#

تسعّر الطرد دون إنشائه. تقبل الحقول نفسها التي تقبلها POST /parcels، وcompany_id مطلوب.

bash
curl -X POST https://YOUR_DOMAIN/api/v1/quote \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Accept: application/json" \
  -H "Accept-Language: en" \
  -d '{ "company_id": 13, "recipient": { "governorate": "Damascus" }, "weight": 2 }'
json
{
  "success": true,
  "data": {
    "pricing_mode": "governorate",
    "pricing_label": "Fixed amount per governorate",
    "governorate": "Damascus",
    "distance_km": 0,
    "weight_kg": 2,
    "delivery_fee": 6,
    "platform_fee": 1,
    "charged_now": 7
  }
}

عند التسعير لدى شركة شحن يكون الرد كائناً مختلفاً وأصغر: pricing_mode (القيمة carrier_quote) وcarrier وaccount وcarrier_fee وcarrier_currency وcarrier_fee_usd وplatform_fee وcharged_now وheld_now وrequired_balance وshipping_paid_by وreceiver_pays وnet_after_delivery وdestination وcurrency. ولا يتضمن pricing_label ولا governorate ولا distance_km ولا weight_kg ولا delivery_fee. وإذا تعذّر حساب السعر تحصل على 422 والكود quote_failed. يحسب الخادم السعر. وcharged_now هو المبلغ المخصوم عند الإنشاء: لشركة توصيل هو أجرة التوصيل مع رسوم المنصة، أو رسوم المنصة وحدها لشركة التوصيل الداخلية الخاصة بك.

ما يجب إرساله يتوقف على pricing_mode الخاص بالشركة:

pricing_mode السعر ما يجب إرساله
governorate الرسم الأساسي مع سعر الشركة لمحافظة المستلم recipient.governorate (وإن غاب تحاول المنصة استنتاجها من العنوان ثم من الإحداثيات)
per_kg الرسم الأساسي مع الوزن مضروباً بسعر الكيلوغرام weight
per_km الرسم الأساسي مع المسافة مضروبة بسعر الكيلومتر recipient.lat وrecipient.lng؛ ويجب أن يكون لمكان الاستلام موقع على الخريطة
km_kg الرسم الأساسي مع رسوم المسافة والوزن الإحداثيات وweight
carrier_quote سعر شركة الشحن نفسها: تطلبه POST /quote من الشركة (carrier_fee)، ويُطلب مرة أخرى عند إنشاء الطرد الحقول نفسها، مع use_own_account أو shipping_paid_by إن احتجتهما

في التسعير governorate وper_kg وper_km وkm_kg يُقرَّب الوزن إلى كيلوغرام كامل للأعلى (وبحدّ أدنى 1)، ويمكن للشركة تحديد حدّ أدنى للأجرة فلا ينزل السعر عنه. أما في carrier_quote فلا ينطبق أيٌّ منهما: ترسل المنصة إلى شركة الشحن الوزن الذي أرسلته (الافتراضي 1) وهي التي تسعّر الطرد.

POST /parcels#

تنشئ طرداً. تُحسب الأجرة على الخادم وتُخصم من محفظتك. ويمكن أيضاً إرسال حقول المستلم مسطّحة (customer_name وcustomer_phone وcustomer_address وcustomer_city وlat وlong).

الحقل مطلوب القواعد
company_id نعم رقم صحيح من GET /companies؛ ويجب أن تكون الشركة متاحة لك
recipient.name نعم نص حتى 191 حرفاً
recipient.phone نعم حتى 30 حرفاً ويطابق ^[+0-9\s\-()]{6,30}$
recipient.address نعم نص حتى 1000 حرف
recipient.governorate حسب الشركة نص حتى 120 حرفاً؛ ويُحوَّل id من /governorates المُرسل نصاً ("3") إلى اسم، أما الرقم في JSON مثل 3 فيفشل بالرمز 422 على customer_city. ويلزم مع التسعير governorate (وإلا تحاول المنصة استنتاجه من العنوان)
recipient.lat، recipient.lng حسب الشركة أرقام من -90 إلى 90 ومن -180 إلى 180. مطلوبة مع التسعير per_km وkm_kg
reference لا نص حتى 191 حرفاً؛ رقم طلبك، ويجعل إعادة المحاولة آمنة
cod_amount لا مبلغ التحصيل عند الاستلام (COD) الذي يدفعه المستلم: رقم من 0 إلى 100,000,000 دولار أمريكي (يُحوَّل المبلغ المُرسل بعملة غير الدولار إلى الدولار بسعر صرف المنصة قبل التحقق من هذا الحد)؛ الافتراضي 0 (مدفوع مسبقاً)
currency لا رمز عملة cod_amount؛ الافتراضي USD. واحد من USD وSYP وSAR وAED وQAR وKWD وJOD وIQD وEGP وLBP وTRY وEUR
weight لا كيلوغرامات من 0.01 إلى 1000؛ الافتراضي 1
pickup_location_id لا رقم صحيح لأحد أماكن استلامك. يُستخدم المكان الافتراضي عند عدم إرساله
warehouse_items إن كان مكان الاستلام مستودعاً قائمة من {"sku": "A1", "qty": 2} أو {"item_id": 5, "qty": 1}
note لا نص حتى 2000 حرف
use_own_account لا قيمة منطقية؛ الشحن بحسابك الخاص لدى شركة شحن تدعم ذلك
shipping_paid_by لا sender أو customer أو included_in_cod، لشركات الشحن التي تدعم الخيار
wait_for_balance لا قيمة منطقية؛ يبقى الطرد بحالة awaiting_funds بدل الفشل عندما لا يكفي الرصيد
json
{
  "success": true,
  "data": {
    "id": 5521,
    "tracking_number": "DS261013AB12CD",
    "reference": "ORDER-1001",
    "stage": "processing",
    "status_code": 5,
    "status_label": "Received Warehouse",
    "company_id": 13,
    "company_name": "Example Delivery",
    "external_tracking_number": null,
    "recipient": {
      "name": "Ahmad Mohammad",
      "phone": "0933123456",
      "governorate": "Damascus",
      "address": "Mazzeh, building 4",
      "lat": 33.5021,
      "lng": 36.2489
    },
    "cod_amount": 25,
    "cod_collected": 0,
    "weight_kg": 1.5,
    "charges": { "delivery_fee": 6, "platform_fee": 1 },
    "delivery_attempts": 0,
    "note": "Call before arriving",
    "tracking_url": "https://YOUR_DOMAIN/t/DS261013AB12CD",
    "created_at": "2026-10-13T10:00:00+03:00",
    "updated_at": "2026-10-13T10:00:00+03:00"
  }
}
  • النجاح: 201 مع كائن الطرد.
  • reference نفسه مرة أخرى: 200 مع الطرد الموجود و"duplicate": true، فتكون إعادة المحاولة آمنة. والطرود التي أُعيد مبلغها أو انتهت مهلتها لا تُعدّ مكرّرة.
  • الرصيد لا يكفي: 402 والكود insufficient_balance. ومع wait_for_balance: true تحصل بدلاً من ذلك على 201 والمرحلة awaiting_funds؛ ويُخفى الطرد من GET /parcels، وتُكمله مهمة مجدولة تعمل كل ساعة عندما يكفي الرصيد وتلغيه بعد 24 ساعة.
  • العملة: يُحوَّل المبلغ المُرسل بعملة غير الدولار إلى دولار أمريكي بسعر صرف المنصة (وغياب سعر الصرف يعطي 422)، فيكون cod_amount في كائنات الطرد دائماً بالدولار. وقد تقبل الشركة بعض العملات فقط، فيفشل cod_currency برسالة تعدّدها.
  • شركات الشحن الخارجية: إذا رفضت الشركة الطرد فلا يُنشأ شيء ولا يُخصم شيء وتحصل على 422 والكود carrier_error.
  • الباقة: التاجر الذي ليس له باقة فعّالة يُرفض طلبه بـ 403 والكود plan_feature قبل معالجة الطرد، وبلوغ حصة الطرود يعطي 422 على الحقل plan.
  • تظهر أخطاء pickup_location_id تحت الحقل shop_id.

حقول كائن الطرد:

الحقل المعنى
id وtracking_number وreference رقم الطرد ورقم التتبع ومرجعك
stage وstatus_code وstatus_label الحالة المبسّطة ورمز الحالة الدقيق وتسميتها بلغة الطلب
company_id وcompany_name وexternal_tracking_number شركة التوصيل، ورقم تتبع شركة الشحن إن وجد
recipient name وphone وgovernorate وaddress وlat وlng
cod_amount وcod_collected المبلغ المطلوب تحصيله والمبلغ المحصّل، بالدولار
weight_kg وcharges الوزن، وdelivery_fee وplatform_fee
delivery_attempts وnote وtracking_url عدد المحاولات والملاحظة ورابط صفحة التتبع العامة
created_at وupdated_at أوقات بصيغة ISO 8601
stage رموز الحالة المعنى
awaiting_funds 1، أثناء انتظار الرصيد محفوظ بانتظار رصيدك
pending 1 بانتظار الاستلام
processing كل حالة أخرى، كالاستلام والمستودع والفروع لدى شركة التوصيل
out_for_delivery 7 و8 مع مندوب التوصيل
delivered 9 تم التسليم
partially_delivered 32 تسليم جزئي؛ وفي cod_collected المبلغ المحصّل
returning 11 و12 و24 و26 و27 فشل التوصيل وهو في طريق العودة
returned 13 و30 عاد إليك

GET /parcels#

تعرض طرودك من الأحدث إلى الأقدم دون events. معاملات الاستعلام: reference وfrom وto (YYYY-MM-DD، بحسب تاريخ الإنشاء) وstage وper_page (من 1 إلى 100، الافتراضي 20) وpage. يقبل فلتر stage القيم pending وout_for_delivery وdelivered وpartially_delivered وreturning وreturned؛ وأي قيمة أخرى، ومنها processing وawaiting_funds، تُهمَل. ولا تظهر في القائمة الطرود التي مرحلتها awaiting_funds.

GET /parcels/{tracking}‎#

تقبل رقم التتبع أو reference (وتُعتمد أحدث نتيجة مطابقة). الرد هو كائن الطرد مع events بترتيب زمني. والطرد غير الموجود يعطي 404 والكود not_found.

json
"events": [
  { "status_code": 1, "stage": "pending", "status_label": "Pending", "note": "Parcel created (api)", "at": "2026-10-13T10:00:00+03:00" },
  { "status_code": 5, "stage": "processing", "status_label": "Received Warehouse", "note": null, "at": "2026-10-13T10:00:01+03:00" }
]

POST /parcels/{tracking}/cancel#

تلغي الطرد وتسترجع ما خُصم عند إنشائه. تنجح فقط عندما تكون الحالة «بانتظار الاستلام» أو «بالمستودع — جاهز للتوزيع» ولا يكون للطرد external_tracking_number؛ وإلا تحصل على 409 والكود not_cancellable. يُحذف الطرد، والرد هو {"tracking_number": "...", "cancelled": true, "refunded": true}. والإلغاء عبر الـ API لا يرسل ويب هوك parcel.cancelled.

كشف الحساب#

GET /statement#

كشف محفظتك لفترة محدّدة: الرصيد الافتتاحي والختامي لكل محفظة، وملخص لكل فئة، وكل الحركات. المبالغ بالدولار.

معاملات الاستعلام: from وto (YYYY-MM-DD؛ الافتراضي من أول الشهر إلى اليوم، وبحدّ أقصى 366 يوماً) وwallet (recharge أو held أو return أو payout_pending) وcategory (مثل parcel_created) وper_page (من 1 إلى 500، الافتراضي 100) وpage. تُهمَل التواريخ غير الصالحة والمحافظ غير المعروفة، وإذا كان from بعد to فيُبدَّلان.

json
{
  "success": true,
  "data": {
    "period": { "from": "2026-10-01", "to": "2026-10-31" },
    "currency": "USD",
    "wallets": [
      { "wallet": "recharge", "name": "Shipping balance", "opening": 50, "credits": 200, "debits": 132.5, "closing": 117.5, "count": 41 }
    ],
    "by_category": [
      { "category": "parcel_created", "name": "Parcel creation fee", "credits": 0, "debits": 132.5, "count": 38 }
    ],
    "lines": [
      { "id": 9811, "date": "2026-10-02T10:14:00+03:00", "wallet": "recharge", "wallet_name": "Shipping balance",
        "type": "parcel_created", "type_name": "Parcel creation fee", "credit": 0, "debit": 3.5, "balance": 46.5,
        "description": "...", "parcel": "DS261002AB12CD" }
    ]
  },
  "meta": { "page": 1, "per_page": 100, "total": 41, "last_page": 1 }
}

نقاط الويب هوك#

تدير هذه النقاط الروابط التي تستقبل إشعاراتك. أما شكل الرسالة والتوقيع وإعادة المحاولة فمشروحة في مقالات الويب هوك.

الطريقة والمسار ماذا تفعل
GET /webhooks/events تعرض الأحداث: event وname وdescription
GET /webhooks تعرض روابطك (ولا تتضمن السرّ أبداً)
POST /webhooks تنشئ رابطاً وتُرجع secret مرة واحدة
PATCH /webhooks/{id} تعدّل name أو url أو events أو active؛ وإعادة تفعيل active تصفّر عدّاد الفشل
DELETE /webhooks/{id} تحذفه: {"deleted": true}
POST /webhooks/{id}/test ترسل ping: {"delivered": true, "response_status": 200}، أو 422 والكود delivery_failed
bash
curl -X POST https://YOUR_DOMAIN/api/v1/webhooks \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Accept: application/json" \
  -d '{ "name": "ERP", "url": "https://example.com/hooks/delivery", "events": ["parcel.delivered", "parcel.returned"] }'
json
{
  "success": true,
  "data": {
    "id": 7, "name": "ERP", "url": "https://example.com/hooks/delivery",
    "events": ["parcel.delivered", "parcel.returned"], "active": true,
    "disabled_reason": null, "last_status": null, "last_error": null, "last_sent_at": null,
    "secret": "whsec_xxxxxxxxxxxxxxxx"
  }
}
  • url مطلوب عند الإنشاء: https فقط، حتى 500 حرف، ومضيف عام، دون اسم مستخدم أو كلمة سر، والمنفذ 443 أو 8443.
  • events مطلوب عند الإنشاء: قائمة فيها عنصر واحد على الأقل، كل عنصر * أو اسم حدث دقيق: parcel.created أو parcel.status_changed أو parcel.out_for_delivery أو parcel.delivered أو parcel.returning أو parcel.returned أو parcel.cancelled أو parcel.awaiting_funds أو wallet.topup أو wallet.withdrawal. وأسماء المجموعات مثل parcel.* مرفوضة هنا، مع أن اللوحة تقبلها.
  • name اختياري (حتى 100 حرف) وactive قيمة منطقية اختيارية.
  • يتطلب POST /webhooks أن تتضمن باقتك ميزة الويب هوك (403 والكود plan_feature)، ويمكنك امتلاك 10 روابط كحدّ أقصى أو حدّ باقتك إن كان أقل (422 والكود limit_reached). ولا تتحقق بقية نقاط الويب هوك من هذه الميزة، لكن المنصة لا ترسل رسائل الأحداث إلى روابطك إذا خلت باقتك منها.

أمثلة برمجية#

إنشاء طرد#

bash
curl -X POST https://YOUR_DOMAIN/api/v1/parcels \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Accept: application/json" \
  -d '{
    "company_id": 13,
    "reference": "ORDER-1001",
    "recipient": {
      "name": "Ahmad Mohammad",
      "phone": "0933123456",
      "governorate": "Damascus",
      "address": "Mazzeh, next to the mosque, building 4",
      "lat": 33.5021,
      "lng": 36.2489
    },
    "cod_amount": 25,
    "weight": 1.5,
    "note": "Call before arriving"
  }'
javascript
async function createParcel() {
  const res = await fetch("https://YOUR_DOMAIN/api/v1/parcels", {
    method: "POST",
    headers: {
      "Authorization": `Bearer ${process.env.DS_API_KEY}`,
      "Content-Type": "application/json",
      "Accept": "application/json",
    },
    body: JSON.stringify({
      company_id: 13,
      reference: "ORDER-1001",
      recipient: {
        name: "Ahmad Mohammad",
        phone: "0933123456",
        governorate: "Damascus",
        address: "Mazzeh, building 4",
      },
      cod_amount: 25,
      weight: 1.5,
    }),
  });
  const json = await res.json();
  if (!json.success) {
    throw new Error(`${res.status} ${json.error.code}: ${json.error.message}`);
  }
  console.log(json.data.tracking_number, json.data.tracking_url, json.duplicate === true);
}

createParcel();
php
<?php
$payload = [
    'company_id' => 13,
    'reference'  => 'ORDER-1001',
    'recipient'  => [
        'name'        => 'Ahmad Mohammad',
        'phone'       => '0933123456',
        'governorate' => 'Damascus',
        'address'     => 'Mazzeh, building 4',
    ],
    'cod_amount' => 25,
    'weight'     => 1.5,
];

$ch = curl_init('https://YOUR_DOMAIN/api/v1/parcels');
curl_setopt_array($ch, [
    CURLOPT_POST           => true,
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_TIMEOUT        => 30,
    CURLOPT_HTTPHEADER     => [
        'Authorization: Bearer ' . getenv('DS_API_KEY'),
        'Content-Type: application/json',
        'Accept: application/json',
    ],
    CURLOPT_POSTFIELDS     => json_encode($payload),
]);
$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']}");
}
echo $body['data']['tracking_number'], "\n";
python
import os
import requests

BASE = "https://YOUR_DOMAIN/api/v1"
HEADERS = {
    "Authorization": f"Bearer {os.environ['DS_API_KEY']}",
    "Accept": "application/json",
}

payload = {
    "company_id": 13,
    "reference": "ORDER-1001",
    "recipient": {
        "name": "Ahmad Mohammad",
        "phone": "0933123456",
        "governorate": "Damascus",
        "address": "Mazzeh, building 4",
    },
    "cod_amount": 25,
    "weight": 1.5,
}

r = requests.post(f"{BASE}/parcels", json=payload, headers=HEADERS, timeout=30)
body = r.json()
if not body["success"]:
    raise RuntimeError(f"{r.status_code} {body['error']['code']}: {body['error']['message']}")
print(body["data"]["tracking_number"], body.get("duplicate", False))

عرض الطرود وتتبّعها#

bash
curl "https://YOUR_DOMAIN/api/v1/parcels?stage=out_for_delivery&per_page=50&page=1" \
  -H "Authorization: Bearer YOUR_API_KEY" -H "Accept: application/json"

curl "https://YOUR_DOMAIN/api/v1/parcels/ORDER-1001" \
  -H "Authorization: Bearer YOUR_API_KEY" -H "Accept: application/json"
javascript
async function listAndTrack() {
  const base = "https://YOUR_DOMAIN/api/v1";
  const headers = { Authorization: `Bearer ${process.env.DS_API_KEY}`, Accept: "application/json" };

  const list = await (await fetch(`${base}/parcels?stage=out_for_delivery&per_page=50`, { headers })).json();
  console.log(list.meta.total, list.data.map((p) => p.tracking_number));

  const one = await (await fetch(`${base}/parcels/ORDER-1001`, { headers })).json();
  if (one.success) {
    console.log(one.data.stage, one.data.events.map((e) => e.status_label));
  } else if (one.error.code === "not_found") {
    console.log("No parcel with this number or reference");
  }
}

listAndTrack();
php
<?php
function ds_get(string $path): array
{
    $ch = curl_init('https://YOUR_DOMAIN/api/v1' . $path);
    curl_setopt_array($ch, [
        CURLOPT_RETURNTRANSFER => true,
        CURLOPT_TIMEOUT        => 30,
        CURLOPT_HTTPHEADER     => ['Authorization: Bearer ' . getenv('DS_API_KEY'), 'Accept: application/json'],
    ]);
    $body = json_decode(curl_exec($ch), true);
    curl_close($ch);

    return $body;
}

$list = ds_get('/parcels?' . http_build_query(['stage' => 'out_for_delivery', 'per_page' => 50]));
echo $list['meta']['total'], "\n";

$one = ds_get('/parcels/ORDER-1001');
if ($one['success']) {
    echo $one['data']['stage'], "\n";
}
python
import os
import requests

BASE = "https://YOUR_DOMAIN/api/v1"
HEADERS = {"Authorization": f"Bearer {os.environ['DS_API_KEY']}", "Accept": "application/json"}

page = requests.get(f"{BASE}/parcels", params={"stage": "out_for_delivery", "per_page": 50}, headers=HEADERS, timeout=30).json()
print(page["meta"]["total"], [p["tracking_number"] for p in page["data"]])

one = requests.get(f"{BASE}/parcels/ORDER-1001", headers=HEADERS, timeout=30).json()
if one["success"]:
    print(one["data"]["stage"], [e["status_label"] for e in one["data"]["events"]])

أخطاء شائعة#

  • غياب company_id. يعطي 422؛ استدعِ GET /companies أولاً.
  • تسعير حسب المحافظة دون محافظة، أو حسب المسافة دون إحداثيات. 422 على customer_city أو lat أو shop_id. استعلم عن السعر عبر POST /quote قبل الإنشاء.
  • توقّع طرد جديد عند تكرار reference. تحصل على الطرد الأول مع "duplicate": true.
  • الفلترة بـ stage=processing أو awaiting_funds. يُهمَل الفلتر وتُرجَع كل الطرود.
  • الاشتراك بـ parcel.* عبر الـ API. 422؛ اذكر الأحداث بأسمائها أو استخدم *.
  • قراءة cod_amount على أنه بالعملة التي أرسلتها. هو دائماً بالدولار.
  • الاعتماد على نص الرسالة. استخدم error.code.

الحدود والصلاحيات#

  • لا يستخدم هذه الواجهة إلا تجّار المنصة، ويجب أن تتضمن الباقة ميزة الـ API؛ ويحتاج إنشاء رابط ويب هوك (POST /webhooks) أيضاً إلى ميزة الويب هوك.
  • حدود المسارات في الجدول أعلاه (عدّاد واحد مشترك لكل عنوان IP، وليست حصصاً منفصلة). وحدود الباقة في الدقيقة وفي اليوم تعطي 429 والكود rate_limited، أما الحد العام وحدود المسارات فتعطي 429 مع {"message": "Too Many Attempts."}.
  • أحجام الصفحات: الطرود من 1 إلى 100، وكشف الحساب من 1 إلى 500.
  • تُخصم الأجور من رصيد الشحن؛ والباقة ذات حصة الطرود تمنع الإنشاء عند استهلاك الحصة.

API

  • GET /api/v1/me
  • GET /api/v1/companies
  • GET /api/v1/governorates
  • GET /api/v1/pickup-locations
  • GET /api/v1/warehouse/stock
  • POST /api/v1/quote
  • POST /api/v1/parcels
  • GET /api/v1/parcels
  • GET /api/v1/parcels/{tracking}
  • POST /api/v1/parcels/{tracking}/cancel
  • GET /api/v1/statement
  • GET /api/v1/webhooks/events
  • GET /api/v1/webhooks
  • POST /api/v1/webhooks
  • PATCH /api/v1/webhooks/{id}
  • DELETE /api/v1/webhooks/{id}
  • POST /api/v1/webhooks/{id}/test