כל בקשה (למעט נקודת הקצה של וידג'ט ההטמעה עצמו) עוברת אימות באחת משתי דרכים: JWT של צד ראשון (המשתמשים שלכם מתחברים דרך הממשק שלכם) או מפתח API (אינטגרציות בין שרתים). שתיהן מסתכמות באותו `RequestContext` — מזהה דייר, ואחריו חברות (membership) או תת-קבוצת ההרשאות של מפתח API — ולכן כל נקודת קצה מתנהגת בדיוק אותו הדבר, בלי קשר למי שקרא לה.

## תהליך JWT לצד ראשון

Institflow היא מערכת רב-דיירית: חשבון משתמש אחד יכול להחזיק חברות בכמה מכונים, ולכן ההתחברות היא לחיצת יד בת שלושה שלבים ולא קריאה בודדת.

1. `POST /api/v1/auth/login` עם `email`, `password` ו-`client` (`"web"` או `"mobile"`). התשובה נושאת `accessToken` קצר-מועד, את ה-`user` המחובר, וכל `membership` פעילה שהחשבון מחזיק. אם יש בדיוק אחת, `activeTenantId` כבר מוגדר ואפשר לדלג על שלב 2.
2. `POST /api/v1/auth/select-tenant` עם ה-`membershipId` שנבחר — מחזיר `accessToken` חדש התחום לאותו דייר. הקשר הדייר של כל בקשה עוקבת מגיע מהטוקן הזה, לעולם לא מכותרת או מפרמטר שאילתה שאתם קובעים בעצמכם.
3. `POST /api/v1/auth/refresh` מחליף את ה-refresh token ומנפיק `accessToken` חדש לפני שהישן פג. refresh token שמוצג פעמיים (שימוש חוזר — סימן חזק לכך שנגנב) מבטל מיד את כל משפחת הסשן (`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
```

מפתח קשור לדייר של החברות שיצרה אותו ונושא רק את ה-`permissions` שלו עצמו — לא את מלוא ההרשאות שלכם — בכל בקשה שהוא מאמת. ביטול מפתח (`DELETE /api/v1/api-keys/:id`) נכנס לתוקף מיד, לא אחרי שמטמון כלשהו יפוג.

## מפתחות לפרסום

סוג מפתח שני, `ifl_pub_<…>`, בטוח להטמעה בקוד צד לקוח — הוא יכול *רק* לפנות למשטח `/api/v1/public/*`: `POST /public/registrations` (נקודת הקצה שבה משתמש [וידג'ט ההרשמה](https://docs.institflow.com/he/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`.
- אותו מפתח, עם אותם method/נתיב/גוף כמו קודם ← התשובה המקורית מושמעת שוב בית-בבית (בלי ביצוע חוזר — בטוח לנסות שוב בקשה שפג לה הזמן בלי לדעת מה קרה).
- אותו מפתח, עם method/נתיב/גוף *שונים* ← `409` `IDEMPOTENCY_KEY_REUSED` — השתמשתם שוב במפתח עבור בקשה אחרת.
- אותו מפתח, שמגיע *בזמן שהבקשה הראשונה עדיין רצה* ← `409` `IDEMPOTENCY_KEY_REUSED`. הפעולה מתבצעת פעם אחת בלבד; נסו שוב אחרי שהקריאה הראשונה חוזרת.
- גם `4xx` נזכר — כישלון דטרמיניסטי מושמע שוב במקום להתבצע מחדש. לעומת זאת `5xx` **לעולם** אינו נשמר במטמון: ניסיון חוזר באמת מבצע את הבקשה מחדש, וזה בדיוק מה שרוצים אחרי כשל בצד השרת.

המפתחות נזכרים למשך 24 שעות.

> **טביעת האצבע כוללת את מחרוזת השאילתה**
>
> שתי בקשות נחשבות “אותה בקשה” רק כאשר ה-method שלהן, הכתובת המלאה *כולל מחרוזת השאילתה*, והגוף זהים בית-בבית — טביעת האצבע היא `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_…`) בטוח בקוד שגלוי לדפדפן.
