مرجع واجهة التاجر 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#
تُرجع بيانات حسابك، وهي أول استدعاء مناسب للتأكد من المفتاح.
{
"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 عند التسعير أو إنشاء الطرد.
{
"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 مطلوب.
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 }'
{
"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 بدل الفشل عندما لا يكفي الرصيد |
{
"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.
"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 فيُبدَّلان.
{
"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 |
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"] }'
{
"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). ولا تتحقق بقية نقاط الويب هوك من هذه الميزة، لكن المنصة لا ترسل رسائل الأحداث إلى روابطك إذا خلت باقتك منها.
أمثلة برمجية#
إنشاء طرد#
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"
}'
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
$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";
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))
عرض الطرود وتتبّعها#
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"
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
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";
}
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/meGET /api/v1/companiesGET /api/v1/governoratesGET /api/v1/pickup-locationsGET /api/v1/warehouse/stockPOST /api/v1/quotePOST /api/v1/parcelsGET /api/v1/parcelsGET /api/v1/parcels/{tracking}POST /api/v1/parcels/{tracking}/cancelGET /api/v1/statementGET /api/v1/webhooks/eventsGET /api/v1/webhooksPOST /api/v1/webhooksPATCH /api/v1/webhooks/{id}DELETE /api/v1/webhooks/{id}POST /api/v1/webhooks/{id}/test