→ العودة إلى لوحة التحكم

📘 دليل ربط API — منصّة البصمات

💡 اكتب رابط خادمك فتتحدّث كل روابط النقاط بالأسفل تلقائياً وتصبح جاهزة للنسخ بضغطة. (التوكن يُستخدم في أمثلة المصادقة فقط، لا يُكتب في كل رابط.)

١. مقدمة وبداية سريعة

تتيح هذه البوابة لنظامك سحب بيانات الحضور/البصمات الخاصة بمؤسستك وإدارة أجهزتها، عبر توكن واحد خاص بمؤسستك. كل طلب يُحدِّد مؤسستك تلقائياً — لن ترى إلا بياناتك.

كل المسارات تبدأ بـ /api/v1. أول تجربة — تحقّق من توكنك:

curl -H "Authorization: Bearer __TOKEN__" \
  __BASE__/api/v1/me

إن رجع "ok": true ومعلومات مؤسستك فالتوكن يعمل.

٢. المصادقة

أرسل التوكن في ترويسة Authorization مع كل طلب:

Authorization: Bearer __TOKEN__
🔒 التوكن يُعرض مرة واحدة عند إنشائه. إذا تسرّب، دوّره من لوحة التحكم (صفحة «المؤسسات») — يبطل القديم فوراً.

٣. سحب البيانات (قراءة)

GET__BASE__/api/v1/me

معلومات مؤسستك + أعداد الأجهزة والطلاب.

GET__BASE__/api/v1/devices

أجهزتك (بصمات مؤسستك) وحالة اتصالها الآن (online حيّ، وليس قيمة قديمة). يُرجع لكل جهاز sn وname وonline — استخدم sn لاختيار الأجهزة عند إضافة مستخدم في «إدارة الأجهزة».

GET__BASE__/api/v1/students

الطلاب/الموظفون المسجّلون على أجهزتك (الرقم والاسم).

GET__BASE__/api/v1/attendance

سجلات الحضور/البصمات (الأحدث أولاً) مع ترقيم بالمؤشّر (cursor).

المعاملالوصف
limitعدد السجلات (١–٥٠٠، الافتراضي ١٠٠)
sinceمن تاريخ (ISO مثل 2026-06-23T00:00:00Z)
untilإلى تاريخ (ISO)
cursorمؤشّر الصفحة التالية (من nextCursor)

مثال الرد:

{
  "ok": true,
  "attendance": [
    {
      "sn": "STC010083287",
      "enrollid": 1,
      "name": "علي رياض",
      "time": "2026-06-19 14:51:06",
      "eventAt": "2026-06-19T11:51:06.000Z",
      "inout": 0,
      "mode": 8,
      "imageUrl": "/images/..._.jpg",
      "createdAt": "2026-06-19T11:51:07.000Z"
    }
  ],
  "hasMore": true,
  "nextCursor": "eyJ0Ijox..."
}

inout: 0 = دخول، 1 = خروج · mode: 8/15 = وجه، 0/1 = بصمة إصبع.

٤. إدارة الأجهزة

أوامر تُرسَل للجهاز وتُرجع فوراً 202 مع commandId (لا تنتظر رد الجهاز). أوامر جهاز واحد (الأسفل) تتطلّب أن يكون الجهاز متّصلاً (وإلا 409)، أمّا إضافة مستخدم للمؤسسة فتُحفظ في طابور دائم وتصل للأجهزة غير المتصلة عند عودتها.

POST__BASE__/api/v1/students

إضافة/تسجيل مستخدم إلى مؤسستك وأجهزتها المختارة دفعةً واحدة. نوع الجسم: multipart/form-data (لرفع صورة الوجه كملف). الحقول:

الحقلالوصف
enrollidرقم المستخدم — مطلوب
nameالاسم
admin0 مستخدم · 1 مدير (يدخل قائمة إعدادات الجهاز)
passwordكلمة مرور رقمية بلا أصفار بادئة (اختياري)
cardرقم بطاقة رقمي بلا أصفار بادئة (اختياري)
imageملف صورة الوجه JPEG/PNG (اختياري) — تُسجَّل على كل جهاز مختار، كإضافة الصورة في الواجهة
devicesالأرقام التسلسلية للأجهزة (من GET /api/v1/devices) — كرّر الحقل لكل جهاز. احذفه = كل أجهزة المؤسسة. غير المملوكة تُهمَل وتظهر في skipped

لا يشترط اتصال الأجهزة — تصلها الإضافة عند عودتها.

مثال الاستعمال (form-data عبر curl):

curl -X POST "__BASE__/api/v1/students" \
  -H "Authorization: Bearer __TOKEN__" \
  -F "enrollid=20" \
  -F "name=طالب جديد" \
  -F "admin=0" \
  -F "password=1234" \
  -F "card=123456" \
  -F "image=@face.jpg" \
  -F "devices=STC010083286" \
  -F "devices=STC010099001"

مثال الرد:

{
  "ok": true,
  "enrolled": 2,
  "devices": [
    { "sn": "STC010083286", "online": true },
    { "sn": "STC010099001", "online": false }
  ],
  "skipped": []
}

بديل برمجي: يمكنك أيضاً الإرسال بصيغة application/json — مع devices كمصفوفة وimage كنص data:URL (base64).

PATCH__BASE__/api/v1/students/:enrollid

تعديل مستخدم في مؤسستك: الاسم / الصلاحية (admin) / كلمة المرور / البطاقة / صورة الوجه، وكذلك أجهزته. نفس حقول الإضافة (multipart/form-data لرفع صورة، أو application/json). مرّر devices لضبط مجموعة الأجهزة (يُضاف للجديدة ويُحذف من المحذوفة)، أو احذفه لإبقائها كما هي. الحقول غير المُرسَلة تبقى دون تغيير. لحذف البطاقة أرسل الحقل removeCard=true.

مثال (تغيير الصورة والصلاحية):

curl -X PATCH "__BASE__/api/v1/students/20" \
  -H "Authorization: Bearer __TOKEN__" \
  -F "admin=1" \
  -F "image=@new-face.jpg" \
  -F "devices=STC010083286"
DELETE__BASE__/api/v1/students/:enrollid

حذف المستخدم نهائياً من المؤسسة وإزالته من كل أجهزتها. (لحذفه من جهاز واحد فقط استخدم DELETE /api/v1/devices/:sn/users/:enrollid بالأسفل.)

POST__BASE__/api/v1/devices/:sn/users

تسجيل/تعديل مستخدم على جهاز واحد محدّد (يتطلّب اتصال الجهاز). backupnum: 0–9 بصمة · 10 كلمة مرور · 11 بطاقة · 20–27 وجه · 50 صورة وجه.

الجسم (JSON): {"enrollid":20,"name":"طالب جديد","backupnum":0}

DELETE__BASE__/api/v1/devices/:sn/users/:enrollid

حذف مستخدم. أضف ?backupnum= لحذف قالب محدّد (الافتراضي: الكل).

POST__BASE__/api/v1/devices/:sn/open-door

فتح الباب (لأجهزة التحكم بالدخول).

الجسم (اختياري): {"doornum":1}

POST__BASE__/api/v1/devices/:sn/sync

سحب فوري من الجهاز. what: newlog (سجلات جديدة) أو userlist (المستخدمون) أو both.

الجسم (JSON): {"what":"both"}

٥. البث اللحظي (Real-time stream)

بدل السحب المتكرر، افتح اتصال WebSocket واحد فيصلك كل حضور لحظة حدوثه — معزولاً لمؤسستك فقط (توكنك يحدّد مؤسستك، ولا ترى أحداث غيرها).

WS__WSBASE__/api/v1/stream

المصادقة بترويسة Authorization: Bearer (للخوادم) أو ?token= (للمتصفح، لأنه لا يدعم ترويسات في WebSocket).

خطوات الربط

  1. افتح اتصال WebSocket إلى __WSBASE__/api/v1/stream
  2. المصادقة: من خادمك أرسل ترويسة Authorization: Bearer ‹توكنك›؛ ومن المتصفح أضِف ?token=‹توكنك› إلى الرابط (المتصفح لا يدعم الترويسات في WebSocket).
  3. عند النجاح تصلك أولاً رسالة {"type":"connected"} بمعلومات مؤسستك، ثم تتدفّق أحداث الحضور لحظة حدوثها.
  4. عالِج الانقطاع: أعِد الاتصال تلقائياً عند close (كما في مثال Node أدناه)، والخادم يرسل نبضة ping كل ٣٠ ثانية للإبقاء على الاتصال.
  5. توكن خاطئ أو معطّل ⟵ يُرفض الاتصال بالرمز 401.

أنواع الأحداث الواردة:

{ "type": "connected", "institution": { "id": "...", "name": "...", "slug": "..." } }

{ "type": "attendance", "sn": "STC010083287", "enrollid": 1, "name": "علي رياض",
  "time": "2026-06-19 14:51:06", "inout": 0, "mode": 8, "at": "2026-06-19T11:51:07.000Z" }

{ "type": "device", "sn": "STC010083287", "online": true, "at": "..." }

Node.js (خادم — بترويسة Authorization)

const WebSocket = require("ws");

function connect() {
  const ws = new WebSocket("__WSBASE__/api/v1/stream", {
    headers: { Authorization: "Bearer __TOKEN__" }
  });
  ws.on("message", (raw) => {
    const ev = JSON.parse(raw);
    if (ev.type === "attendance")
      console.log("حضور:", ev.enrollid, ev.name, ev.time, ev.inout ? "خروج" : "دخول");
  });
  ws.on("close", () => setTimeout(connect, 3000)); // أعد الاتصال تلقائياً
  ws.on("error", () => {});
}
connect();

المتصفح (التوكن في الرابط)

const ws = new WebSocket("__WSBASE__/api/v1/stream?token=__TOKEN__");
ws.onmessage = (e) => {
  const ev = JSON.parse(e.data);
  if (ev.type === "attendance") console.log("حضور:", ev.name, ev.time);
};
💓 يرسل الخادم نبضة (ping) كل ٣٠ ثانية للحفاظ على الاتصال. عالج الانقطاع بإعادة اتصال تلقائية كما في مثال Node أعلاه. توكن خاطئ → يُرفض الاتصال بـ 401.

٦. سحب الحضور تدريجياً (المؤشّر)

لجلب الجديد فقط في كل مرة: استخدم nextCursor من الرد السابق كـ cursor للطلب التالي، ودُر طالما hasMore = true. احفظ آخر مؤشّر لتبدأ منه لاحقاً.

⚠️ إذا تعطّلت القاعدة مؤقتاً، تُعاد بعض البصمات لاحقاً بترتيب وصول مختلف. للضمان: أعد المسح دورياً من since أقدم بقليل، أو طابِق السجلات بمفتاح enrollid + time.

٧. أمثلة تكامل بلغات مختلفة

سحب كل الحضور صفحةً صفحة:

const BASE = "__BASE__";
const TOKEN = "__TOKEN__";

async function pullAll(cursor) {
  let url = `${BASE}/api/v1/attendance?limit=200`;
  if (cursor) url += `&cursor=${encodeURIComponent(cursor)}`;
  const res = await fetch(url, { headers: { Authorization: `Bearer ${TOKEN}` } });
  const data = await res.json();
  for (const rec of data.attendance) {
    // خزّن rec في نظامك — المفتاح الفريد: enrollid + time
    console.log(rec.enrollid, rec.name, rec.time);
  }
  if (data.hasMore) return pullAll(data.nextCursor);
  return data.nextCursor; // احفظه لتبدأ منه لاحقاً
}
pullAll();
<?php
$BASE  = "__BASE__";
$TOKEN = "__TOKEN__";

function pull($BASE, $TOKEN, $cursor = null) {
  $url = "$BASE/api/v1/attendance?limit=200";
  if ($cursor) $url .= "&cursor=" . urlencode($cursor);
  $ch = curl_init($url);
  curl_setopt($ch, CURLOPT_HTTPHEADER, ["Authorization: Bearer $TOKEN"]);
  curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
  $data = json_decode(curl_exec($ch), true);
  curl_close($ch);
  foreach ($data["attendance"] as $rec) {
    // خزّن $rec في نظامك (enrollid + time مفتاح فريد)
  }
  if (!empty($data["hasMore"])) return pull($BASE, $TOKEN, $data["nextCursor"]);
  return $data["nextCursor"];
}
pull($BASE, $TOKEN);
import requests

BASE  = "__BASE__"
TOKEN = "__TOKEN__"
H = {"Authorization": f"Bearer {TOKEN}"}

def pull(cursor=None):
    params = {"limit": 200}
    if cursor: params["cursor"] = cursor
    data = requests.get(f"{BASE}/api/v1/attendance", headers=H, params=params).json()
    for rec in data["attendance"]:
        print(rec["enrollid"], rec["name"], rec["time"])  # خزّنه في نظامك
    if data.get("hasMore"):
        return pull(data["nextCursor"])
    return data.get("nextCursor")

pull()

٨. الأخطاء والحدود

كل الأخطاء بالشكل: {"ok": false, "error": "...", "message": "..."}

الكودالمعنى
400معامل ناقص أو غير صالح
401توكن ناقص أو خاطئ
404الجهاز غير موجود أو ليس ملكاً لمؤسستك
409device_offline — الجهاز غير متصل الآن
429تجاوزت حد الطلبات
202أمر الإدارة قُبل وأُرسل للجهاز

الحدود: القراءة ١٢٠ طلب/دقيقة · الإدارة ٣٠ طلب/دقيقة لكل توكن.

٩. ملاحظات مهمة

  • العزل مضمون: توكنك يرى بيانات مؤسستك فقط.
  • عدّة أجهزة: كل أجهزة مؤسستك تعمل بنفس التوكن.
  • الصور: imageUrl مرجعي فقط، لا يُجلب عبر هذه البوابة في الإصدار الحالي.
  • المفتاح الفريد للسجل: sn + enrollid + time (قد تتكرر بصمتان في نفس الثانية بمؤشّر مختلف).
  • استخدم HTTPS دائماً في الإنتاج.