كل طلب (باستثناء نقطة نهاية أداة التسجيل القابلة للتضمين) يُصادَق بإحدى طريقتين: JWT للتطبيقات الأولى (تسجيل دخول مستخدميك عبر واجهتك الخاصة)، أو مفتاح API (للتكاملات بين الخوادم). كلتاهما تنتهيان إلى نفس سياق الطلب (`RequestContext`) — معرّف مستأجر، وإمّا عضوية أو مجموعة صلاحيات خاصة بمفتاح API — لذا تتصرّف كل نقطة نهاية بنفس الطريقة أيًّا كانت جهة الاستدعاء.

## تدفّق JWT للتطبيقات الأولى

Institflow متعدّد المستأجرين: يمكن لحساب مستخدم واحد أن يحمل عضويات في عدّة معاهد، لذا تسجيل الدخول مصافحة من ثلاث خطوات وليس استدعاءً واحدًا.

1. `POST /api/v1/auth/login` مع `email` و`password` و`client` (بقيمة `"web"` أو `"mobile"`). تحمل الاستجابة `accessToken` قصير الأجل، وبيانات `user` المسجّل دخوله، وكل `membership` نشطة يملكها الحساب. إن كانت عضوية واحدة فقط، يكون `activeTenantId` مُحدَّدًا مسبقًا ويمكنك تخطّي الخطوة الثانية.
2. `POST /api/v1/auth/select-tenant` مع `membershipId` المختارة — تُعيد `accessToken` جديدًا مرتبطًا بذلك المستأجر. سياق المستأجر لكل طلب لاحق يأتي من هذا الرمز، وليس من ترويسة أو معطى استعلام تضبطه بنفسك.
3. `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) له، فإذا فقدته، ألغِه وأنشئ مفتاحًا جديدًا.

```http
Authorization: Bearer ifl_live_9c1f2e7a4b8d3c6f0a5e9b2d7c4f1a8e
```

يرتبط المفتاح بمستأجر العضوية التي أنشأته، ويحمل صلاحياته الخاصة فقط — لا مجموعتك الفعلية الكاملة — مع كل طلب يُصادِق عليه. إلغاء مفتاح (`DELETE /api/v1/api-keys/:id`) يسري فورًا، لا بعد انتهاء صلاحية ذاكرة تخزين مؤقت.

## المفاتيح القابلة للنشر

نوع ثانٍ من المفاتيح، `ifl_pub_<…>`، آمن لتضمينه في شيفرة الواجهة الأمامية — لا يستطيع سوى استدعاء سطح `/api/v1/public/*`: `POST /public/registrations` (النقطة التي تستخدمها [أداة التسجيل](https://docs.institflow.com/ar/widget))، و`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 للتطبيقات الأولى فيمكنهم إرسالها أيضًا، لكنها اختيارية بالنسبة لهم.

- غيابها في طلب تعديلي مصادَق بمفتاح ← `428` `IDEMPOTENCY_KEY_REQUIRED`.
- نفس المفتاح، نفس الطريقة/المسار/المحتوى كسابقه ← تُعاد الاستجابة الأصلية حرفيًا (دون إعادة التنفيذ — آمن لإعادة محاولة طلب انتهت مهلته دون معرفة نتيجته).
- نفس المفتاح، بطريقة/مسار/محتوى *مختلف* ← `409` `IDEMPOTENCY_KEY_REUSED` — أعدت استخدام المفتاح لطلب مختلف.
- نفس المفتاح يصل *بينما الطلب الأول ما يزال قيد التنفيذ* ← `409` `IDEMPOTENCY_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` هو دائمًا الحدّ الفعلي لذلك المفتاح:

```http
RateLimit-Limit: 600
RateLimit-Remaining: 587
RateLimit-Reset: 42
```

تجاوز الحدّ يُعيد `429` برمز الخطأ `RATE_LIMITED` — تمهّل حتى تمرّ الثواني المذكورة في `RateLimit-Reset`.

> **لا تشارك مفتاحًا سرّيًا أبدًا**
>
> المفتاح السرّي (`ifl_live_…`) يُصادِق بأي صلاحيات مُنحت له — تعامل معه كأنّه كلمة مرور. المفتاح القابل للنشر (`ifl_pub_…`) فقط هو الآمن في شيفرة مرئية للمتصفّح.
