المصادقة
كل طلب (باستثناء نقطة نهاية أداة التسجيل القابلة للتضمين) يُصادَق بإحدى طريقتين: JWT للتطبيقات الأولى (تسجيل دخول مستخدميك عبر واجهتك الخاصة)، أو مفتاح API (للتكاملات بين الخوادم). كلتاهما تنتهيان إلى نفس سياق الطلب (RequestContext) — معرّف مستأجر، وإمّا عضوية أو مجموعة صلاحيات خاصة بمفتاح API — لذا تتصرّف كل نقطة نهاية بنفس الطريقة أيًّا كانت جهة الاستدعاء.
تدفّق JWT للتطبيقات الأولى
Institflow متعدّد المستأجرين: يمكن لحساب مستخدم واحد أن يحمل عضويات في عدّة معاهد، لذا تسجيل الدخول مصافحة من ثلاث خطوات وليس استدعاءً واحدًا.
POST /api/v1/auth/loginمعemailوpasswordوclient(بقيمة"web"أو"mobile"). تحمل الاستجابةaccessTokenقصير الأجل، وبياناتuserالمسجّل دخوله، وكلmembershipنشطة يملكها الحساب. إن كانت عضوية واحدة فقط، يكونactiveTenantIdمُحدَّدًا مسبقًا ويمكنك تخطّي الخطوة الثانية.POST /api/v1/auth/select-tenantمعmembershipIdالمختارة — تُعيدaccessTokenجديدًا مرتبطًا بذلك المستأجر. سياق المستأجر لكل طلب لاحق يأتي من هذا الرمز، وليس من ترويسة أو معطى استعلام تضبطه بنفسك.POST /api/v1/auth/refreshيُجدِّد رمز التحديث (refresh token) ويصدرaccessTokenجديدًا قبل انتهاء صلاحية القديم. رمز تحديث يُقدَّم مرّتين (إعادة استخدام — إشارة قوية إلى سرقته) يُلغي عائلة الجلسة بأكملها فورًا (SESSION_REVOKED)؛ سجّل الدخول من جديد في هذه الحالة.
أرسل رمز الوصول كـ Authorization: Bearer <accessToken> مع كل طلب. GET /api/v1/auth/me يُعيد المستخدم الحالي وعضوياته وقائمة صلاحياته الفعلية في المستأجر النشط — مفيد لبناء واجهة جلستك الخاصة دون إعادة اشتقاق الصلاحيات على العميل.
مفاتيح API السرّية
للتكاملات بين الخوادم (سكربت، مهمّة خلفية، نظام شريك) أنشئ مفتاحًا سرّيًا بدلًا من ذلك: POST /api/v1/api-keys (يتطلّب صلاحية integration.manage) مع name ومصفوفة permissions — مجموعة فرعية صارمة من صلاحياتك الفعلية؛ لا يمكنك أبدًا منح المفتاح أكثر ممّا تملكه أنت. يُعاد المفتاح الكامل (ifl_live_<32 بايت عشوائي، بترميز base64url>) مرّة واحدة فقط عند الإنشاء — لا يُخزَّن سوى بادئته وقيمة تجزئة (hash) له، فإذا فقدته، ألغِه وأنشئ مفتاحًا جديدًا.
Authorization: Bearer ifl_live_9c1f2e7a4b8d3c6f0a5e9b2d7c4f1a8eيرتبط المفتاح بمستأجر العضوية التي أنشأته، ويحمل صلاحياته الخاصة فقط — لا مجموعتك الفعلية الكاملة — مع كل طلب يُصادِق عليه. إلغاء مفتاح (DELETE /api/v1/api-keys/:id) يسري فورًا، لا بعد انتهاء صلاحية ذاكرة تخزين مؤقت.
المفاتيح القابلة للنشر
نوع ثانٍ من المفاتيح، ifl_pub_<…>، آمن لتضمينه في شيفرة الواجهة الأمامية — لا يستطيع سوى استدعاء سطح /api/v1/public/*: POST /public/registrations (النقطة التي تستخدمها أداة التسجيل)، وPOST /public/leads (طلبات نموذج التواصل)، وفهرس الدورات للقراءة فقط (GET /public/courses، /public/courses/:id، /public/branches — وتردّ هذه بـ 404 إلى أن يفعّل المعهد الفهرس العام). يُرسَل في الترويسة X-Api-Key، وهو مقيّد بقائمة نطاقات مصرّح بها تضبطها لكل مفتاح، ولا يحمل أي صلاحيات موظفين، ومحدود المعدّل حسب عنوان IP وحسب المفتاح. لا تستخدم مفتاحًا قابلًا للنشر لأي غرض آخر — فهو عمدًا غير قادر على شيء آخر.
ترويسة Idempotency-Key
كل طلب POST/PUT/PATCH/DELETE يُرسَل بمفتاح API يجب أن يحمل ترويسة Idempotency-Key (أي نص فريد — عادةً UUID). أمّا مستدعو JWT للتطبيقات الأولى فيمكنهم إرسالها أيضًا، لكنها اختيارية بالنسبة لهم.
- غيابها في طلب تعديلي مصادَق بمفتاح ←
428IDEMPOTENCY_KEY_REQUIRED. - نفس المفتاح، نفس الطريقة/المسار/المحتوى كسابقه ← تُعاد الاستجابة الأصلية حرفيًا (دون إعادة التنفيذ — آمن لإعادة محاولة طلب انتهت مهلته دون معرفة نتيجته).
- نفس المفتاح، بطريقة/مسار/محتوى مختلف ←
409IDEMPOTENCY_KEY_REUSED— أعدت استخدام المفتاح لطلب مختلف. - نفس المفتاح يصل بينما الطلب الأول ما يزال قيد التنفيذ ←
409IDEMPOTENCY_KEY_REUSED. العملية تُنفَّذ مرّة واحدة فقط؛ أعد المحاولة بعد انتهاء الطلب الأول. - أخطاء
4xxتُحفَظ أيضًا — الفشل الحتمي يُعاد بثّه بدل إعادة تنفيذه. أمّا5xxفلا يُحفَظ أبدًا: إعادة المحاولة تُنفّذ الطلب فعلًا، وهو السلوك المطلوب بعد فشل من جهة الخادم.
تُحفَظ المفاتيح لمدة 24 ساعة.
البصمة تشمل سلسلة الاستعلام
يُعدّ طلبان «الطلب نفسه» فقط إذا تطابقت الطريقة والمسار الكامل بما فيه سلسلة الاستعلام والمحتوى حرفًا بحرف — فالبصمة هي sha256(METHOD + "\n" + url + "\n" + body). إعادة محاولة تُغيّر ترتيب معاملات الاستعلام تُعدّ طلبًا مختلفًا بالمفتاح نفسه، فتحصل على 409 بدل الاستجابة المعادة. رتّب معاملاتك بالطريقة نفسها في كلّ مرّة.
حدود معدّل الطلبات
كل طلب محدود المعدّل بنافذة ثابتة في Redis — حسب عنوان IP لغير المصادَقين، حسب المستخدم لمستدعي JWT، وحسب المفتاح لمستدعي API (600 طلب/دقيقة افتراضيًا). يمكن منح المفتاح حدًّا أضيق خاصًّا به عبر rateLimitPerMin في POST /api/v1/api-keys أو PATCH /api/v1/api-keys/{id} — وهو مفيد لمفتاح تسلّمه لطرف ثالث. ولا يمكن أبدًا أن يكون أوسع من سقف خطتك؛ طلب أكثر من ذلك يُعيد 422. كل استجابة تحمل حالة النافذة الحالية، وRateLimit-Limit هو دائمًا الحدّ الفعلي لذلك المفتاح:
RateLimit-Limit: 600
RateLimit-Remaining: 587
RateLimit-Reset: 42تجاوز الحدّ يُعيد 429 برمز الخطأ RATE_LIMITED — تمهّل حتى تمرّ الثواني المذكورة في RateLimit-Reset.
لا تشارك مفتاحًا سرّيًا أبدًا
المفتاح السرّي (ifl_live_…) يُصادِق بأي صلاحيات مُنحت له — تعامل معه كأنّه كلمة مرور. المفتاح القابل للنشر (ifl_pub_…) فقط هو الآمن في شيفرة مرئية للمتصفّح.