اسأل، أو دعنا نخبرك، أو خذ كل شيء.
لبيانات الأسطول ثلاث طرق تصل بها إلى أنظمتك، ومعظم التكاملات التي تفشل اختارت الطريقة الخطأ: خريطة مباشرة مبنية على استعلام كل مركبة على حدة، أو مستودع بيانات يغذيه webhooks فيفقد يوما كلما أعيد تشغيل المستقبل. وهنا كيف تختار، والحساب الذي وراء الاختيار.
REST API
المواقع والرحلات والأحداث والأصول، موثقة، بترقيم صفحات بالمؤشر وحد لمعدل الطلبات.
webhooks
موقع، يعاد إرساله مع تباعد زمني، وقابل للإعادة لمدة 72 ساعة.
ERP والتشغيل
أرسل المهام إلى النظام، واسحب منه إثبات التسليم ومعدل الاستخدام.
تصدير البيانات
ملفات CSV أو Parquet مجدولة إلى مساحة تخزين تملكها. بياناتك تبقى لك.
أيها يناسب أسطولك
أول تكامل تكتبه معظم الفرق يطلب موقع كل مركبة على حدة. ينجح على أسطول الاختبار ويسقط على الأسطول الحقيقي. حرك الشريط إلى النقطة التي لا يعود أسطولك يتسع عندها داخل 600 طلب في الدقيقة.
اسحب التدفق بمؤشر
طلب واحد يعيد كل ما تغير منذ الطلب السابق، عبر الأسطول كله. سهل البناء، سهل التصحيح، ولا شيء يضيع بين الطلبات.
حد المعدل: 600 طلب في الدقيقة لكل مفتاح
- أحداث webhook يوميا
- 13,600
- تصدير يومي
- 1,440,000 صف · 20 ميجابايت
كيف تبدو الطلبات
اسأل: تدفق المؤشر
طلب واحد، والأسطول كله، وكل شيء منذ المرة الماضية.
GET https://api.nml.sa/fleet/v1/positions?since=cur_7Hd91K&limit=1000
200 OK
{ "data": [ …up to 1000 rows… ],
"next": "cur_7Hd9Qx", // pass this as ?since next time
"has_more": false } // true: call again straight away أخبرنا: ادفع مهمة
أعد المحاولة بلا قلق، فمفتاح Idempotency-Key يعني أن المهمة تنشأ مرة واحدة.
POST https://api.nml.sa/fleet/v1/jobs
Idempotency-Key: ERP-SO-448213
{ "ref": "SO-448213", "vehicle": "VAN-042",
"stop": { "position": [21.5433, 39.1728], "window": ["10:00", "12:00"] } } لتصلك البيانات: webhook
موقع، مع حفظ وقت التسجيل ووقت الاستلام معا.
{
"id": "evt_01J8Q4Z6K3",
"type": "trip.completed",
"vehicle": {
"id": "TRK-114",
"plate": "4821 RSA"
},
"trip": {
"started_at": "2026-09-14T05:12:40+03:00",
"ended_at": "2026-09-14T08:47:10+03:00",
"km": 212.4,
"driver_id": "DRV-3107"
},
"last_position": {
"position": [
24.7136,
46.6753
],
"speed": 0,
"ignition": false,
"recorded_at": "2026-09-14T08:47:10+03:00",
"received_at": "2026-09-14T08:47:12+03:00"
}
} العنوان الأساسي · https://api.nml.sa/fleet/v1
حين يتوقف endpoint لديك طوال نهاية الأسبوع
يعاد إرسال التسليم الفاشل 9 مرات، والفاصل بين المحاولات يطول في كل مرة، وآخرها بعد 46.7 h من أول إخفاق. ويبقى كل شيء قابلا لإعادة الإرسال 72 ساعة.
إعادة المحاولة وحدها لا تغطي انقطاعا مدته 48 ساعة في عطلة نهاية الأسبوع، إذ تقف على بعد 1.3 ساعة منه. أما نافذة إعادة الإرسال فتغطيه وتزيد 24 ساعة: صباح الأحد تعيد الإرسال من ليلة الخميس فلا يضيع شيء.
- #1 · 30 ثانية
- #2 · 3 دقائق
- #3 · 13 دقيقة
- #4 · 43 دقيقة
- #5 · 1.7 h
- #6 · 4.7 h
- #7 · 10.7 h
- #8 · 22.7 h
- #9 · 46.7 h
تحقق من أنه جاء منا
HMAC قياسي، بلا حاجة إلى مكتبة. ارفض الطوابع الزمنية القديمة، وقارن في زمن ثابت، فلا يعاد إرسال طلب ملتقط إليك.
// Every webhook carries: Saqr-Signature: t=<unix seconds>,v1=<hex>
// v1 is HMAC-SHA256 of "<t>.<raw body>" with your endpoint secret.
async function verify(raw: string, header: string, secret: string) {
const { t, v1 } = Object.fromEntries(
header.split(",").map((p) => p.split("=") as [string, string])
);
// Reject anything older than five minutes: a captured request replayed later.
if (Math.abs(Date.now() / 1000 - Number(t)) > 300) return false;
const key = await crypto.subtle.importKey(
"raw", new TextEncoder().encode(secret),
{ name: "HMAC", hash: "SHA-256" }, false, ["sign"]
);
const mac = await crypto.subtle.sign("HMAC", key, new TextEncoder().encode(`${t}.${raw}`));
const hex = [...new Uint8Array(mac)].map((b) => b.toString(16).padStart(2, "0")).join("");
// Compare every character, so the time taken says nothing about where they differ.
let diff = hex.length ^ v1.length;
for (let i = 0; i < hex.length; i++) diff |= hex.charCodeAt(i) ^ (v1.charCodeAt(i) || 0);
return diff === 0;
}
// Deliveries are at-least-once: store evt ids and ignore a second arrival. الأحداث
-
trip.startedمفتاح التشغيل يعمل والمركبة تتحرك -
trip.completedمفتاح التشغيل متوقف منذ دقيقتين -
geofence.entered / exitedعبور حد رسمته -
alert.raisedأي قاعدة تنبيه، مع درجة خطورتها -
unit.silentلا بيانات بعد حد التقادم -
job.statusسائق ينقل مهمة إلى مرحلتها التالية في التطبيق
كل موقع يحمل
- position
- سرعة
- heading
- ignition
- odometer
- fuel_pct
- driver_id
- accel
- gsm
- recorded_at
- received_at
في أي باقة
| Essential | Professional | Command | |
|---|---|---|---|
| REST API وwebhooks | قراءة فقط | قراءة فقط | كامل |
| تصدير بيانات مجدول |
الصفوف نفسها التي في جدول الأسعار، مقروءة منه. والتصدير المجدول في باقة Command وحدها، أما تصدير سجلك كاملا مرة واحدة فمتاح في كل باقة، لأنه بياناتك.
ما نعد به وما لا نعد به
- مرة واحدة على الأقل، لا مرة واحدة بالضبط
- قد يصل webhook مرتين بعد إعادة المحاولة. لكل حدث معرف id، فاحتفظ بما رأيته منها.
- الترتيب غير مضمون
- التعبئة اللاحقة بعد منطقة بلا تغطية تسلم مواقع قديمة متأخرة. رتب حسب recorded_at لا حسب وقت وصولها.
- لا نبني التكامل عنك
- نوثق، ونجيب عن الأسئلة، وننظر في سجلاتك معك. أما الكود عندك فهو لك، وخطته لك.
- الكتابة على باقة Command فقط
- القراءة متاحة في كل الباقات. أما إدخال المهام والسائقين والأسيجة الجغرافية، والتصدير المجدول، فتحتاج إلى باقة Command.