LOUAJI EXPRESS API · v1
دليل الربط البرمجي
لربط متجركم أو نظام شركتكم بلواجي إكسبريس: تُنشئون الشحنة من نظامكم، وتتابعون حالتها حتى التسليم، دون إدخال يدوي. الدليل نفسه للتجار وللشركات الشريكة — الفرق في القسم الأخير.
بداية سريعة
الربط كله أربع خطوات، وأول مرة فقط:
- احصل على مفتاح من لوحتك: التكامل ← «مفتاح جديد».
- اسحب قائمة الوجهات مرة واحدة واربط كل مدينة عندك بـ
destinationIdعندنا. - أنشئ الشحنة عند كل طلب، وأرسل معها رقم طلبك في
reference. - تابع الحالة عبر الويبهوك (نُخبرك فور التغيّر) أو بسحب التغييرات كل بضع دقائق.
كل الطلبات JSON على العنوان https://api.raseel.express، والمفتاح يُرسَل في ترويسة X-API-Key مع كل طلب.
المفتاح والمصادقة
المفتاح يبدأ بـ pk_live_ ويُنشأ من لوحتك. يظهر مرة واحدة عند إنشائه، فاحفظه في إعدادات نظامك ولا تضعه في كود يصل إليه المتصفح — أي طلب يُرسَل من خادمك لا من صفحة الزبون.
X-API-Key: pk_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
- يمكنك إنشاء أكثر من مفتاح (للتجربة وللإنتاج) وإيقاف أيٍّ منها لحظياً من اللوحة.
- إيقاف المفتاح يوقف الطلبات فوراً ولا يمسّ الشحنات المُنشأة به.
- الحد الأقصى 60 طلباً في الدقيقة. تجاوزه يردّ
429— استخدم الدفعات بدل طلب لكل شحنة. - إذا تسرّب المفتاح: أوقفه من اللوحة وأنشئ غيره. لا حاجة لمراجعتنا.
الوجهات — تُربط مرة واحدة
لا ترسل اسم مدينة مكتوباً بخط يدك في كل طلب. اسحب القائمة مرة واحدة، واربط كل منطقة عندك بمعرّفها عندنا، ثم أرسل destinationId دائماً. هذا وحده هو الفرق بين ربط ينتهي في ساعة وربط يرفض نصف الطلبات بسبب اختلاف في كتابة الاسم.
/api/v1/integration/destinations— قائمة المناطق التي نوصل إليها{
"count": 2,
"destinations": [
{ "id": 42, "name": "جرابلس", "city": "جرابلس", "governorate": "حلب", "type": "CITY_CENTER", "branch": "فرع حلب" },
{ "id": 43, "name": "الأشرفية", "city": "حلب", "governorate": "حلب", "type": "CITY_CENTER", "branch": "فرع حلب" }
]
}governorate هي المحافظة التي تخدم المنطقة، وليست دائماً مدينتها: منبج بلدة قائمة بذاتها ضمن محافظة حلب. استخدمها إن كنت تعرض قائمتنا مجمّعة في نظامك.
إن أرسلت city باسم لا نعرفه، يردّ الخادم 422 ومعه suggestions بأقرب المناطق، لتصحّح الربط دون مراسلتنا.
إنشاء شحنة
/api/v1/integration/parcels— الرد 201الحقول:
| الحقل | النوع | إلزامي؟ | الشرح |
|---|---|---|---|
| reference | نص | إلزامي | رقم الطلب في نظامكم. يمنع التكرار، وبه تستعلمون لاحقاً بدل رقم التتبع. |
| receiverName | نص | إلزامي | اسم المستلم، حرفان على الأقل. |
| receiverPhone | نص | إلزامي | هاتف المستلم، 6 خانات على الأقل. |
| receiverAddress | نص | إلزامي | العنوان التفصيلي داخل المدينة. |
| destinationId | رقم | إلزامي أو city | معرّف المنطقة من قائمة الوجهات. الأدقّ، ولا يتأثر بطريقة كتابة الاسم. |
| city | نص | بديل عن destinationId | اسم المنطقة أو المدينة كما تعرفونه. يُطابَق مع مناطقنا بالاسم ثم بالمدينة. |
| codAmount | رقم | اختياري | المبلغ المطلوب تحصيله من المستلم. صفر أو تركه فارغاً يعني شحنة بلا تحصيل. |
| codCurrency | SYP · USD · TRY | اختياري | عملة التحصيل. الافتراضي SYP. |
| shippingPaidBy | customer · included_in_cod · sender | اختياري | من يدفع أجرة التوصيل. الافتراضي included_in_cod. التفاصيل في قسم «من يدفع الأجرة». |
| weight | رقم | اختياري | الوزن بالكيلوغرام. |
| description | نص | اختياري | وصف المحتوى، مثل «ملابس». |
| notes | نص | اختياري | ملاحظة للسائق أو للفرع، مثل «يُسلَّم بعد العصر». |
| barcode | نص | اختياري | باركود ملصقكم إن وُجد — يُمسح كما هو في فروعنا. |
| senderCompany | نص | اختياري | اسم المتجر داخل نظامكم. للشركات الشريكة التي تشحن لعدة متاجر. |
مثال الطلب:
{
"reference": "ORD-88213",
"receiverName": "إبراهيم الناقو",
"receiverPhone": "0981563850",
"receiverAddress": "شارع الفرات - بناء 5",
"destinationId": 42,
"codAmount": 150000,
"codCurrency": "SYP",
"shippingPaidBy": "included_in_cod",
"weight": 1.5,
"description": "ملابس",
"notes": "يُسلَّم بعد العصر"
}مثال الرد:
{
"trackingNumber": "RSL-20260923-K91B",
"reference": "ORD-88213",
"senderCompany": null,
"status": "PENDING",
"subStatus": null,
"receiver": {
"name": "إبراهيم الناقو",
"phone": "0981563850",
"city": "جرابلس",
"address": "شارع الفرات - بناء 5"
},
"cod": { "amount": "150000", "currency": "SYP" },
"shipping": { "fee": "470", "currency": "SYP", "paidBy": "included_in_cod" },
"createdAt": "2026-09-23T08:14:11.000Z",
"deliveredAt": null,
"returnedAt": null,
"destination": { "id": 42, "name": "جرابلس", "branch": "فرع حلب", "matchedBy": "id" }
} reference. لن تُنشأ شحنة ثانية، وسيعود الرد نفسه ومعه duplicate: true. لهذا السبب reference إلزامي.الأجرة تُحسب من تسعيرتك المتفق عليها وتعود في shipping.fee بعملة التحصيل. لا ترسل سعراً من عندك — السعر يُقرَّر عندنا حسب عقدك.
دفعة شحنات
حتى 200 شحنة في طلب واحد. النتيجة لكل شحنة على حدة: خطأ في واحدة لا يُسقط الباقي، والرد يقول أيّها فشلت ولماذا.
/api/v1/integration/parcels/batch— الرد 207POST https://api.raseel.express/api/v1/integration/parcels/batch
{ "parcels": [ { ... }, { ... } ] } // حتى 200 طرد
// الرد 207 — نتيجة لكل طرد على حدة
{
"accepted": 1,
"failed": 1,
"results": [
{ "index": 0, "result": "ok", "trackingNumber": "RSL-…", "reference": "ORD-1" },
{ "index": 1, "result": "failed", "reference": "ORD-2", "error": "هاتف المستلم مطلوب" }
]
}الاستعلام والمتابعة
شحنة واحدة — برقم طلبك أو برقم التتبع:
/api/v1/integration/parcels/{reference}كل ما تغيّر منذ آخر مرة — الطريقة الموصى بها لمزامنة آلاف الشحنات بطلب واحد كل بضع دقائق. احفظ cursor من كل رد وأرسله في since في المرة التالية.
/api/v1/integration/parcels-changed?since=…&limit=200— الحد الأقصى 500GET https://api.raseel.express/api/v1/integration/parcels-changed?since=2026-09-23T08:00:00Z&limit=200
{
"count": 2,
"cursor": "2026-09-23T09:41:02.883Z", // احفظوه وأرسلوه في since المرة القادمة
"more": false,
"parcels": [ { "trackingNumber": "…", "status": "DELIVERED", "updatedAt": "…" } ]
}وللزبون نفسه: صفحة تتبع عامة بلا مفتاح على raseel.express/track — ضَع رابطها في رسالة التأكيد التي ترسلها لزبونك.
التعديل والإلغاء
يمكن التعديل والإلغاء ما دامت الشحنة في حالة PENDING، أي قبل تأكيد استلامها في الفرع. بعد الاستلام تكون البضاعة في عهدتنا، فيتم التعديل عبر الفرع لا عبر الـ API، ويردّ الخادم 403 برسالة تقول ذلك.
/api/v1/integration/parcels/{reference}— اسم المستلم، الهاتف، العنوان، الوجهة، التحصيل/api/v1/integration/parcels/{reference}— إلغاء الشحنةالحالات
الحالة تصل في status، وحالات الإرجاع تُفصَّل في subStatus.
| status | المعنى |
|---|---|
| PENDING | أُنشئت الشحنة ولم تصل الفرع بعد. |
| IN_BRANCH | استُلمت في الفرع. |
| IN_TRANSIT | بين الفروع. |
| OUT_FOR_DELIVERY | خرجت مع السائق للتوصيل. |
| DELIVERED | سُلّمت للمستلم. |
| DELIVERY_FAILED | تعذّر التسليم — راجعوا failureReason. |
| RETRY_SCHEDULED | محاولة تسليم جديدة مجدولة. |
| RETURNED | قيد الإرجاع — راجعوا subStatus. |
| CANCELLED | أُلغيت قبل الاستلام. |
حالات الإرجاع الفرعية:
| subStatus | المعنى |
|---|---|
| RETURN_REQUESTED | طُلب الإرجاع. |
| RETURNED_TO_BRANCH | عادت إلى الفرع. |
| RETURNED_TO_SENDER | سُلّمت لكم — انتهت رحلة الشحنة. |
عند DELIVERY_FAILED يصل معها failureReason، وهي اللحظة التي يُنتظر فيها قرارك: إعادة محاولة أم إرجاع.
من يدفع أجرة التوصيل
هذا الاختيار يقرّر مالاً، ولذلك لا نخمّنه عنك. القيمة الافتراضية included_in_cod.
| القيمة | المستلم يدفع | ماذا يصلك |
|---|---|---|
| customer | التحصيل + الأجرة | التحصيل كاملاً؛ الأجرة يدفعها المستلم عند الباب. |
| included_in_cod | التحصيل فقط | التحصيل ناقص الأجرة — الأجرة من داخل المبلغ. |
| sender | التحصيل فقط | التحصيل كاملاً، والأجرة تُقيَّد على حسابك وتُسوّى في كشفك. |
إن كان التحصيل بعملة والأجرة بأخرى، لا تُخصم إحداهما من الأخرى: تُقيَّد الأجرة على حسابك وتظهر في الكشف صراحةً.
الويبهوك — نُخبرك فور تغيّر الحالة
بدل أن تسأل، نُرسل إليك. سجّل رابطاً واحداً من لوحتك، واختر الأحداث. نرسل POSTبصيغة JSON، ومعه توقيع في ترويسة X-Sham-Signature.
| الحدث | متى يُرسَل |
|---|---|
| parcel.status_changed | عند كل تغيّر في الحالة حتى التسليم أو الإرجاع. |
| parcel.created | عند إنشاء الشحنة. |
| parcel.delivered | عند التسليم فقط. |
| collection.available | عند إضافة مبلغ التحصيل إلى محفظتك. |
| * | كل الأحداث. |
شكل الرسالة:
{
"id": 1842,
"event": "parcel.status_changed",
"timestamp": "2026-09-23T09:41:02.883Z",
"data": { "trackingNumber": "RSL-…", "reference": "…", "status": "DELIVERED", "previousStatus": "OUT_FOR_DELIVERY", "…": "…" }
}تحقّق من التوقيع دائماً — هو ما يمنع أي جهة من انتحال إشعاراتنا:
// Node.js — تحققوا من التوقيع قبل الوثوق بأي طلب
import crypto from "crypto";
app.post("/raseel/webhook", express.raw({ type: "application/json" }), (req, res) => {
const signature = crypto
.createHmac("sha256", process.env.RASEEL_WEBHOOK_SECRET)
.update(req.body) // النص الخام قبل أي تحويل
.digest("hex");
if (signature !== req.header("X-Sham-Signature")) return res.sendStatus(401);
const { id, event, timestamp, data } = JSON.parse(req.body);
// id: رقم الرسالة، نفسه عند كل إعادة محاولة — تجاهلوا ما عالجتموه سابقاً
// event: "parcel.status_changed" مثلاً
// data: الشحنة، وفيها trackingNumber و status (حالتها عند هذا التغيّر)
res.sendStatus(200); // ردّوا 200 بسرعة، وعالجوا لاحقاً
});<?php // PHP
$raw = file_get_contents("php://input");
$expected = hash_hmac("sha256", $raw, getenv("RASEEL_WEBHOOK_SECRET"));
if (!hash_equals($expected, $_SERVER["HTTP_X_SHAM_SIGNATURE"] ?? "")) {
http_response_code(401); exit;
}
$payload = json_decode($raw, true);- التوقيع = HMAC-SHA256 لنص الرسالة الخام بمفتاح السرّ الذي يظهر لك عند تسجيل الرابط.
- ردّ بـ
200خلال 10 ثوانٍ ثم عالج البيانات في الخلفية. - إن لم يردّ رابطك بـ 2xx نعيد المحاولة بعد دقيقة ثم 5 و15 دقيقة ثم ساعة و3 و6 و12 ساعة. رسائل الشحنة الواحدة تصل بترتيبها، و
timestampوقت التغيّر نفسه. - سجل كل رسالة وردّ رابطك عليها في لوحتك، صفحة «تكامل API».
- الويبهوك تنبيه وليس مصدر الحقيقة: إن تعطّل رابطك، أعِد المزامنة بـ
parcels-changed.
الأخطاء
كل خطأ يعود بصيغة واحدة، ورسالته بالعربية جاهزة للعرض على موظفك:
{ "statusCode": 422, "message": "لا نوصل إلى «كفرنبل» أو أن الاسم غير مطابق. استخدم destinationId من GET /destinations", "code": "UNKNOWN_DESTINATION", "suggestions": [ … ] }| الرمز | المعنى | ما العمل |
|---|---|---|
| 401 | المفتاح مفقود أو موقوف | أرسلوا X-API-Key، وتأكدوا أن المفتاح فعّال في لوحتكم. |
| 400 | حقل ناقص أو غير صالح | الرد يحوي رسالة لكل حقل. صحّحوا وأعيدوا الإرسال. |
| 422 | وجهة غير معروفة — UNKNOWN_DESTINATION | الرد يحوي suggestions بأقرب المناطق. استخدموا destinationId. |
| 404 | لا توجد شحنة بهذا الرقم على حسابكم | تأكدوا من reference أو رقم التتبع. |
| 403 | الشحنة تجاوزت المرحلة التي يُسمح فيها بالتعديل | بعد تأكيد الاستلام في الفرع لا تُعدَّل ولا تُلغى من الـ API — تواصلوا مع الفرع. |
| 429 | تجاوزتم 60 طلباً في الدقيقة | أعيدوا المحاولة بعد قليل، أو استخدموا الدفعات بدل طلب لكل شحنة. |
أمثلة جاهزة
curl
curl -X POST https://api.raseel.express/api/v1/integration/parcels \
-H "X-API-Key: pk_live_xxxxxxxxxxxxxxxx" \
-H "Content-Type: application/json" \
-d '{
"reference": "ORD-88213",
"receiverName": "إبراهيم الناقو",
"receiverPhone": "0981563850",
"receiverAddress": "شارع الفرات - بناء 5",
"destinationId": 42,
"codAmount": 150000,
"codCurrency": "SYP"
}'Node.js
const res = await fetch("https://api.raseel.express/api/v1/integration/parcels", {
method: "POST",
headers: {
"X-API-Key": process.env.RASEEL_KEY,
"Content-Type": "application/json",
},
body: JSON.stringify({
reference: order.id,
receiverName: order.customer.name,
receiverPhone: order.customer.phone,
receiverAddress: order.customer.address,
destinationId: order.zoneId, // من قائمة الوجهات
codAmount: order.total,
codCurrency: "SYP",
}),
});
const parcel = await res.json();
if (!res.ok) throw new Error(JSON.stringify(parcel));
// احفظوا رقم التتبع بجانب الطلب عندكم
await orders.update(order.id, { trackingNumber: parcel.trackingNumber });PHP
<?php
$ch = curl_init("https://api.raseel.express/api/v1/integration/parcels");
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
"X-API-Key: " . getenv("RASEEL_KEY"),
"Content-Type: application/json",
],
CURLOPT_POSTFIELDS => json_encode([
"reference" => $order["id"],
"receiverName" => $order["name"],
"receiverPhone" => $order["phone"],
"receiverAddress" => $order["address"],
"destinationId" => $order["zone_id"],
"codAmount" => $order["total"],
"codCurrency" => "SYP",
], JSON_UNESCAPED_UNICODE),
]);
$parcel = json_decode(curl_exec($ch), true);Python
import os, requests
r = requests.post(
"https://api.raseel.express/api/v1/integration/parcels",
headers={"X-API-Key": os.environ["RASEEL_KEY"]},
json={
"reference": order["id"],
"receiverName": order["name"],
"receiverPhone": order["phone"],
"receiverAddress": order["address"],
"destinationId": order["zone_id"],
"codAmount": order["total"],
"codCurrency": "SYP",
},
timeout=20,
)
r.raise_for_status()
parcel = r.json()الشركات الشريكة
الشركة الشريكة تستخدم نفس واجهة الشحنات أعلاه بنفس المفتاح، ولها فوق ذلك لوحة خاصة على raseel.express/partner.
انتبهوا للفرق: مسارات /api/v1/merchant/integration/… تعمل بـمفتاح الربط، أمّا مسارات /api/v1/partner/… فهي مسارات لوحتكم وتحتاج تسجيل دخول لا مفتاحاً — المفتاح عليها يُرجِع 401.
بمفتاح الربط:
/api/v1/merchant/integration/quote?destinationId=42&codCurrency=SYP— الأجرة قبل إنشاء الشحنة/api/v1/merchant/integration/destinations— الوجهات ومعرّفاتها/api/v1/merchant/integration/parcels-changed?since=— ما تغيّر من شحناتكمبتسجيل دخول لوحة الشريك:
/api/v1/partner/parcels?status=&from=&to=— شحنات الشركة/api/v1/partner/statement— كشف الحساب: لكم وعليكم والصافي/api/v1/partner/wallet— الأرصدة والمتاح للسحب وسجل السحوبات/api/v1/partner/withdrawals— طلب سحب من الرصيد المتاح/api/v1/partner/parcels/{key}/failure-decision— بعد تعذّر التسليم: إعادة محاولة أو إرجاع- أرسلوا
senderCompanyمع كل شحنة باسم المتجر داخل نظامكم. تظهر في تقاريركم لأداء كل متجر، وتُطبع على البوليصة كمرسل. - التحصيل الذي نجمعه لكم دَين علينا يظهر في الكشف، ويُسحب من اللوحة أو عبر الواجهة أعلاه.
- التسعيرة تُتفَق معكم وتُسجَّل على حسابكم؛ لا تُرسَل أسعار مع الشحنة.
قائمة تحقق قبل الإطلاق
- المفتاح محفوظ في إعدادات الخادم، لا في كود الواجهة ولا في مستودع الكود.
- كل مدينة عندكم مربوطة بـ
destinationId، ولا تُرسل أسماء نصية. referenceهو رقم الطلب الحقيقي، وفريد، ويُعاد إرساله عند إعادة المحاولة.- رقم التتبع يُحفظ بجانب الطلب عندكم فور إنشاء الشحنة.
- الويبهوك مسجَّل، والتوقيع يُتحقَّق منه، ومزامنة احتياطية بـ
parcels-changedكل بضع دقائق. shippingPaidByمضبوط على ما اتفقتم عليه، ومُجرَّب على شحنة واحدة قبل الإطلاق.- جُرِّبت حالة الفشل: هاتف ناقص، ووجهة غير معروفة، ومفتاح موقوف.
تحتاج مساعدة في الربط؟
راسلنا من صفحة التواصل ومعك رقم الطلب الذي جرّبته ونصّ الخطأ الذي وصلك — هذان السطران يختصران المحادثة كلها.