# שגיאות

> כל שגיאה שה-SDK של Nodaro ל-TypeScript זורק: סטטוס ה-HTTP, הקוד והשדות, ומה עושים במחסור בקרדיטים, במגבלות קצב, בהתנגשויות ובמשימות שנכשלו.

Source: https://nodaro.ai/he/docs/developers/sdk/errors

כל **שגיאה** שה-SDK של Nodaro זורק בעקבות תשובה של ה-API היא מופע של `NodaroError` או של אחת מתת־המחלקות שלה. בקשה שנכשלת עם סטטוס שגיאה זורקת את תת־המחלקה שמתאימה לסטטוס. פונקציות העזר שמריצות וממתינות זורקות תת־מחלקות משלהן כשמשימה נכשלת, חורגת מהזמן או נעצרת. תפסו קודם את המחלקות הספציפיות, ואת `NodaroError` בסוף.

## כל מחלקות השגיאה
| מחלקה | סטטוס | `code` | שדות נוספים | מתי היא נזרקת |
| --- | --- | --- | --- | --- |
| `NodaroError` | כל סטטוס | הקוד של השרת | | מחלקת הבסיס, וכל שגיאה שאין לה מחלקה ספציפית יותר |
| `UnauthorizedError` | 401 | `unauthorized` | | האסימון חסר, פג תוקפו או לא תקין |
| `ForbiddenError` | 403 | `forbidden` | `missingScope?` | הגישה נדחתה, או שלאסימון OAuth חסרה הרשאה |
| `ModelUnavailableError` | 403 | `model_not_available` או `model_disabled` | | המודל שבחרתם לא מוצע, או שמנהל כיבה אותו |
| `ImportRefusedError` | 403 | `imports_blocked`, `import_source_disabled` או קוד `rights_*` | | ייבוא של קישור נדחה עוד לפני שהתוכן נמשך |
| `NotFoundError` | 404 | `not_found` | | הפריט לא קיים, או שאין לכם גישה אליו |
| `RateLimitedError` | 429 | `rate_limited` | | יותר מדי בקשות |
| `InsufficientCreditsError` | 402 | `insufficient_credits` או קוד 402 אחר | `required?`, `available?` | החשבון לא יכול לשלם על ההרצה |
| `CreditCapExceededError` | 402 | `budget_exceeded`, `member_cap_exceeded` או `user_allowance_exceeded` | `required?`, `available?` | ההרצה חורגת מתקרת הוצאה שמישהו אחר קבע |
| `StorageExceededError` | 413 | `storage_exceeded` | `limitBytes?`, `reason?` | מה ששלחתם לא נכנס: האחסון מלא, או שקובץ אחד חורג מהמגבלה |
| `WorkflowConflictError` | 409 | `workflow_conflict` או `production_busy` | `currentUpdatedAt?`, `currentVersion?`, `currentRecord?` | מישהו אחר שינה את הפריט לפניכם |
| `AlreadyRunningError` | 409 | `already_running` | `executionId` | ההרצה שביקשתם להתחיל או להמשיך עדיין רצה |
| `AppRunVersionChangedError` | 409 | `app_run_version_changed` | `runVersion?`, `appVersion?` | ניסיון חוזר של הרצת אפליקציה נדחה, כי האפליקציה עברה לגרסה אחרת |
| `JobBlockedError` | 422 | `job_blocked` | | מדיניות התוכן של הפריסה דחתה את הבקשה |
| `StudioOpError` | 4xx | הקוד של השרת | `opIndex` | פעולה אחת באצווה של Studio נדחתה |
| `JobFailedError` | 0 | `job_failed` | `jobId`, `jobStatus` | משימה שחיכיתם לה נכשלה או בוטלה |
| `JobTimeoutError` | 0 | `job_timeout` | `jobId`, `timeoutMs` | משימה לא הסתיימה בתוך `maxMs` |
| `JobAbortedError` | 0 | `job_aborted` | `jobId?` | ההמתנה בוטלה דרך ה-`AbortSignal` שלכם |
| `JobHeldError` | 0 | `job_held` | `jobId` | משימה ממתינה לבדיקה של אדם |
| `StudioPreviewUnavailable` | 0 | `studio_preview_unavailable` | | הפריסה לא יכולה להציג תצוגה מקדימה של אצווה של Studio |
| `StudioPreviewAppliedError` | 0 | `studio_preview_applied` | `applied` | אצווה של Studio שביקשתם לראות בתצוגה מקדימה הוחלה בפועל |

לכל מחלקה יש ארבעה שדות: `message`, משפט קריא; `code`, מחרוזת קבועה שאפשר להשוות אליה; `status`, סטטוס ה-HTTP; ו-`details`. הערך `0` ב-`status` פירושו שהשגיאה לא הגיעה מתשובת HTTP. למשל, את `JobTimeoutError` זורקת לולאת התשאול של ה-SDK עצמו.

ב-`details` נמצאים כל השדות של מעטפת השגיאה מלבד `code` ו-`message`, כפי שהשרת שלח אותם. למשל, `slot` כשטרנד נדחה, `reasons` כשפרסום נדחה ו-`issues` כשהקלט לא תקין. הערך הוא `{}` כשלא היה במעטפת שום דבר נוסף, וגם בשגיאה שה-SDK יצר בעצמו.

```ts
try {
await client.trends.run(slug, request)
} catch (err) {
if (err instanceof NodaroError && err.code === "missing_slot") askFor(err.details.slot as string)
}
```

הבנאי הוא `new NodaroError(message, code, status, details?)`.

חלק מהסטטוסים ממופים למחלקה אחת, בלי קשר לקוד שהשרת שלח. שגיאת 403 הופכת ל-`ForbiddenError` עם `code` בערך `forbidden`, חוץ מדחייה של מודל, שהופכת ל-`ModelUnavailableError`, ומדחייה של ייבוא קישור, שהופכת ל-`ImportRefusedError`. שגיאת 404 הופכת ל-`NotFoundError`, וכל שגיאת 413 הופכת ל-`StorageExceededError`. במקרים האלה, את הסיבה של השרת קוראים ב-`message`. שגיאת 409 הופכת ל-`WorkflowConflictError` רק עם הקוד `workflow_conflict` או `production_busy`. שגיאת 409 עם `already_running` שמציינת `executionId` הופכת ל-`AlreadyRunningError`, ושגיאת 409 עם `app_run_version_changed` הופכת ל-`AppRunVersionChangedError`. שגיאות עם כל סטטוס אחר, כגון 400 או 503, ושגיאת 409 עם כל קוד אחר, מגיעות כ-`NodaroError` עם ה-`code` של השרת עצמו.

## תפיסת שגיאות לפי הסדר
```ts

ForbiddenError,
InsufficientCreditsError,
ModelUnavailableError,
NodaroError,
NotFoundError,
RateLimitedError,
StorageExceededError,
UnauthorizedError,
} from "@nodaro/sdk"

try {
await client.workflows.run(workflowId)
} catch (err) {
if (err instanceof UnauthorizedError) {
redirectToLogin()
} else if (err instanceof ModelUnavailableError) {
showError("This model is not available. Choose another model.")
} else if (err instanceof ForbiddenError) {
if (err.missingScope) requestAdditionalScopes([err.missingScope])
else showError("You do not have permission to do this.")
} else if (err instanceof InsufficientCreditsError) {
showCreditPaywall({ required: err.required, available: err.available })
} else if (err instanceof RateLimitedError) {
await retryWithBackoff()
} else if (err instanceof StorageExceededError) {
if (err.reason === "file_too_large") showError("This file is too large.")
else showError(`Storage limit of ${err.limitBytes} bytes reached.`)
} else if (err instanceof NotFoundError) {
showError("Not found.")
} else if (err instanceof NodaroError) {
console.error(`API error ${err.status} (${err.code}): ${err.message}`)
} else {
throw err // a network failure or a timeout, not an API answer
}
}
```

כשבקשה חורגת מ-`timeoutMs` של הלקוח, או כשיש כשל רשת, ה-Promise נדחה עם שגיאה של סביבת הריצה עצמה, כגון `AbortError` או `TypeError`. השגיאות האלה הן לא מופעים של `NodaroError`.

## אימות והרשאות
### UnauthorizedError
HTTP 401. האסימון חסר, פג תוקפו או לא תקין. קבלו אסימון חדש, או בקשו מהמשתמש להתחבר שוב, ואז נסו שוב.

### ForbiddenError
HTTP 403. למי שקרא אין הרשאה לפעולה הזו. כשאסימון OAuth לא קיבל הרשאה שנקודת הקצה דורשת, השדה `missingScope` מציין את ההרשאה הזו, למשל `workflows:execute`. בקשו מהמשתמש לאשר אותה, ואז נסו שוב עם האסימון החדש. ראו את הסעיף [הרשאות (scopes) והרשאות חסרות](https://nodaro.ai/docs/developers/sdk/auth#scopes-and-missing-permissions) בדף אימות.

סיבות אחרות הן, למשל, מהדורה שלא כוללת את היכולת או תפקיד נמוך מדי. כולן מגיעות עם `code` בערך `forbidden`, ולכן הציגו את `message` כדי להסביר מה הסיבה.

### ModelUnavailableError
HTTP 403 שדוחה את המודל, ולא את מי שקרא. Nodaro עונה כך עוד לפני שמשוריינים קרדיטים. בחרו מודל אחר: ניסיון חוזר, טעינת קרדיטים או הרשאה רחבה יותר לא ישנו כלום.

| `code` | סיבת הדחייה |
| --- | --- |
| `model_not_available` | הפריסה הזו לא מציעה את המודל. המודל לא כלול במהדורה או בממשק שלה, או שמנהל השרת הסתיר אותו. |
| `model_disabled` | מנהל כיבה את המודל. |

המחלקה `ModelUnavailableError` מרחיבה את `ForbiddenError`, ולכן בדיקת `instanceof ForbiddenError` שכבר יש בקוד שלכם עדיין תופסת אותה. כדי להבחין בין השתיים, בדקו קודם `instanceof ModelUnavailableError`. השתמשו ב-`code` כדי להציג משפט משלכם, ולא ב-`message` באנגלית. הקבוע `MODEL_UNAVAILABLE_CODES`, מטיפוס `ReadonlySet<ModelUnavailableCode>`, מפרט את שני הקודים. השגיאה לא מציינת את המודל: המודל שנדחה הוא ה-`provider` של הבקשה שלכם, או אחד מה-`models` שלה, כשהבקשה מריצה כמה מודלים. שגיאת 403 עם `insufficient_scope`, עם כל קוד אחר או בלי קוד נשארת `ForbiddenError` רגילה.

הבנאי הוא `new ModelUnavailableError(code, message?)`. הבנאי של `ForbiddenError` הוא `new ForbiddenError(message?, missingScope?, code?)`.

### ImportRefusedError
HTTP 403 שדוחה ייבוא של קישור, עוד לפני שהתוכן נמשך: `media.downloadVideo()`, `uploads.importUrl()` וייבואי האודיו. הסירוב הגיע מאמצעי שהוחל על החשבון, מבדיקת זכויות התוכן או ממדיניות האתרים של הפריסה. ראו את דף ה-API [זכויות תוכן](https://nodaro.ai/docs/developers/api/content-rights) ואת הסעיף [מפרים חוזרים](https://nodaro.ai/docs/developers/api/content-rights#repeat-infringers) באותו דף.

| `code` | סיבת הדחייה |
| --- | --- |
| `imports_blocked` | ייבוא קישורים חסום בחשבון, או שהחשבון סגור, לפי מדיניות המפרים החוזרים של הפריסה. ההודעה מציינת עד מתי: תאריך לפי UTC, או עד שמנהל יסיר את החסימה. |
| `import_source_disabled` | הפריסה לא מאפשרת ייבוא מהאתר של הקישור. ההודעה לא מציינת שום אתר. |
| `rights_confirmation_required` | הפריסה דורשת אישור, ולא נשלח אישור. |
| `rights_authorization_required` | הפריסה דורשת אישור, הייבוא הגיע דרך מפתח API, אפליקציה מחוברת או סוכן AI, ולחשבון אין הרשאה קבועה. בעל החשבון נותן את ההרשאה מתוך הסשן שלו, עם `contentRights.authorize()`. |
| `rights_confirmation_not_found` | אין אישור עם המזהה הזה. |
| `rights_confirmation_mismatch` | האישור שייך לקישור אחר או לחשבון אחר. |
| `rights_confirmation_stale` | ההצהרה השתנתה. טענו אותה שוב ואשרו שוב. |
| `rights_confirmation_expired` | האישור ישן יותר משעה. |
| `rights_confirmation_used` | האישור כבר נוצל לייבוא. גם ייבוא שנכשל אחרי שמשיכת התוכן התחילה מנצל את האישור. |

כשהקוד הוא `rights_confirmation_*`, קראו שוב ל-`client.contentRights.confirm({ url, declarationVersion })` וייבאו שוב עם המזהה החדש. המחלקה `ImportRefusedError` מרחיבה את `ForbiddenError`, ולכן בדיקת `instanceof ForbiddenError` שכבר יש בקוד שלכם עדיין תופסת אותה. בדקו קודם `instanceof ImportRefusedError`, והשתמשו ב-`code` כדי להציג משפט משלכם. הקבוע `IMPORT_REFUSAL_CODES`, מטיפוס `ReadonlySet<ImportRefusalCode>`, מפרט את הקודים. הבנאי הוא `new ImportRefusedError(code, message?)`.

### NotFoundError
HTTP 404. הפריט לא קיים, או שמי שקרא לא יכול לראות אותו. Nodaro עונה באותה דרך בשני המקרים, ולכן מזהה אף פעם לא מגלה אם קיים משהו שאין לכם גישה אליו. משאב שקיים רק ב-Nodaro Cloud, כגון הפקות Studio או Recast, מחזיר 404 גם בהתקנה באירוח עצמי.

## קרדיטים, אחסון ומגבלות
### InsufficientCreditsError
HTTP 402. החשבון לא יכול לשלם על ההרצה, ולכן שום דבר לא התחיל. השדה `required` הוא מספר הקרדיטים שההרצה צריכה, והשדה `available` הוא מספר הקרדיטים שיש בחשבון, או, כשמדובר בתקרה, מה שנשאר בה. Nodaro Cloud ממלא את שניהם, אבל הטיפוס מסמן אותם כאופציונליים. השדה `code` הוא הקוד של הדחייה עצמה: הקוד `insufficient_credits` כשהבעיה היא היתרה שלכם וגם כשהשרת לא שלח קוד, ואחרת קוד 402 אחר, כגון `free_video_limit` או `insufficient_app_credits`.

```ts
try {
await client.nodes.runAndWait("generate-video", params)
} catch (err) {
if (err instanceof InsufficientCreditsError) {
console.log(`Need ${err.required} credits, have ${err.available}`)
}
}
```

בדקו את היתרה לפני הרצה עם [`client.credits.balance()`](https://nodaro.ai/docs/developers/sdk/models-and-credits). ראו את הדף [קרדיטים](https://nodaro.ai/docs/concepts/credits).

### CreditCapExceededError
HTTP 402 בגלל תקרת הוצאה שמישהו אחר קבע, ולא בגלל היתרה שלכם. טעינת קרדיטים לא תעזור: מי שקבע את התקרה צריך להגדיל אותה.

| `code` | התקרה |
| --- | --- |
| `budget_exceeded` | התקציב של סביבת העבודה |
| `member_cap_exceeded` | תקרת הקרדיטים שלכם בסביבת העבודה הזו |
| `user_allowance_exceeded` | המכסה של הפריסה לכל משתמש |

המחלקה `CreditCapExceededError` מרחיבה את `InsufficientCreditsError`, ולכן בדיקת `instanceof InsufficientCreditsError` שכבר יש בקוד שלכם עדיין תופסת אותה. כדי להבחין בין השתיים, בדקו קודם `instanceof CreditCapExceededError`. הקבוע `CREDIT_CAP_CODES`, מטיפוס `ReadonlySet<CreditCapCode>`, מפרט את שלושת הקודים.

### StorageExceededError
HTTP 413. מה ששלחתם לא נכנס. כל שגיאת 413 מגיעה כמחלקה הזו, בכל נתיב, והשדה `reason` מציין באיזה סוג מדובר:

| `reason` | משמעות |
| --- | --- |
| `storage_limit_exceeded` | האחסון של החשבון מלא. השדה `limitBytes` הוא המגבלה. מחקו מדיה שאתם כבר לא צריכים, ואז נסו שוב. |
| `file_too_large` | קובץ אחד חורג ממגבלת הגודל. האחסון לא מלא. שלחו קובץ קטן יותר. |
| `payload_too_large`, `post_too_large`, `duration_exceeded` | הבקשה, הפוסט או אורך המדיה חורגים ממגבלה. האחסון לא מלא. |

כשבתשובה לא היה קוד, השדה `reason` הוא `undefined`. העלאות והעתקות לאחסון שלכם, כגון שכפול מספריית הקהילה, זורקות את השגיאה הזו. הבנאי הוא `new StorageExceededError(message?, limitBytes?, reason?)`.

### RateLimitedError
HTTP 429. שלחתם יותר מדי בקשות. המתינו, ואז נסו שוב עם המתנה שהולכת ומתארכת: למשל 2 שניות, אחר כך 4 ואחר כך 8. הפסיקו אחרי כמה ניסיונות. ראו את דף ה-API [מגבלות קצב](https://nodaro.ai/docs/developers/api/rate-limits).

## המתנה למשימות
המתודות `client.nodes.runAndWait()` ו-`client.nodes.runMany()`, ופונקציות העזר `...AndWait` של משאבים אחרים בודקות את הסטטוס של משימה מדי כמה שניות, עד שהיא מסתיימת. בזמן ההמתנה הן זורקות את השגיאות האלה.

### JobFailedError
המשימה הסתיימה בסטטוס `failed` או `cancelled`. השדה `jobStatus` מציין איזה מהשניים, השדה `jobId` מזהה את המשימה, והשדה `message` כולל את הודעת השגיאה של המשימה עצמה. קבלו את המשימה המלאה עם `client.jobs.get(err.jobId)`: השדה `error_hint` שלה מסביר חסימה של מסנן בטיחות או של מדיניות.

### JobTimeoutError
המשימה לא הגיעה לסטטוס סופי בתוך `maxMs`, שהוא 15 דקות כברירת מחדל. **המשימה לא מבוטלת.** בדרך כלל היא בכל זאת מסתיימת בשרת ונשמרת בספרייה שלכם. בדקו אותה מאוחר יותר עם `client.jobs.get(err.jobId)`, או העבירו `maxMs` גדול יותר למודלים איטיים. משימה שנמצאת בהתאוששות מדווחת `recovering: true` בסטטוס שלה, וההתאוששות יכולה לקחת עשרות דקות.

### JobAbortedError
ההמתנה בוטלה דרך ה-`AbortSignal` שלכם. ה-SDK מפסיק מיד לבדוק את הסטטוס. **המשימה לא מבוטלת.** כדי לעצור אותה בשרת ולקבל החזר על הקרדיטים ששוריינו לה, קראו ל-`client.jobs.cancel(err.jobId)`.

### JobHeldError
המשימה הגיעה לסטטוס `pending_review`: מדיניות תוכן של הפריסה הזו מעכבת את התוצאה עד שאדם יבדוק אותה. ה-SDK מפסיק לחכות כבר בפעם הראשונה שהוא רואה את הסטטוס הזה. המשימה לא מבוטלת, והקרדיטים שלה נשארים משוריינים במהלך הבדיקה. אל תריצו את הבקשה שוב, כי גם בקשה כפולה תמתין לבדיקה.

בדקו את המשימה מאוחר יותר עם `client.jobs.get(err.jobId)`. היא מסתיימת בסטטוס `completed` כשהבודק מאשר אותה, בסטטוס `failed` כשהבודק דוחה אותה, או בסטטוס `cancelled` אם תבטלו אותה. במשימה שנדחתה יש `error_hint.kind` בערך `policy-block`, ו-`reason` שאפשר להציג כמו שהוא. השגיאה הזו קורית רק בפריסות שרושמות מדיניות משימות.

### JobBlockedError
HTTP 422 עם הקוד `job_blocked`. מדיניות תוכן של הפריסה הזו דחתה את הבקשה **לפני שהיא רצה**. לא נוצרה משימה ולא נגבו קרדיטים. השדה `message` נכתב בשביל המשתמשים שלכם, ולכן הציגו אותו כמו שהוא. אל תנסו שוב את אותה בקשה. השגיאה הזו קורית רק בפריסות שרושמות מדיניות משימות.

## שינויים מקבילים
### WorkflowConflictError
HTTP 409. שלחתם שינוי מותנה, ומישהו אחר שינה את הפריט לפניכם. השגיאה מגיעה עם אחד משני קודים:

- הקוד `workflow_conflict`: קריאה ל-[`client.workflows.update()`](https://nodaro.ai/docs/developers/sdk/workflows) עם `expectedVersion` או `expectedUpdatedAt` לא תאמה לתהליך השמור.
- הקוד `production_busy`: הפקת Studio המשיכה להשתנות בזמן שהשרת החיל את השינוי שלכם, והשרת הפסיק לנסות שוב.

הפתרון זהה בשני המקרים: טענו את הפריט מחדש, החילו את השינוי שלכם על העותק העדכני ושלחו אותו שוב. כשהשרת מצרף אותו, ב-`currentRecord` נמצא התהליך הנוכחי, ולכן אפשר למזג בלי לטעון אותו שוב.

```ts

try {
await client.workflows.update(id, { settings, expectedVersion: loadedVersion })
} catch (err) {
if (err instanceof WorkflowConflictError && err.currentRecord) {
const merged = mergeSettings(err.currentRecord.settings, settings)
await client.workflows.update(id, { settings: merged, expectedVersion: err.currentVersion })
} else {
throw err
}
}
```

### AlreadyRunningError
HTTP 409 עם הקוד `already_running`. ההרצה שביקשתם להתחיל או להמשיך עדיין רצה, והשדה `executionId` מזהה אותה. במקום להתחיל אותה שוב, בדקו את המצב שלה עם `client.executions.get(executionId)`. המתודה `client.workflows.run()` זורקת את השגיאה הזו כשהרצה של התהליך כבר בעיצומה. המתודה [`client.apps.resume()`](https://nodaro.ai/docs/developers/sdk/apps-and-templates#appsresumeslug-runid-opts) זורקת אותה כשניסיון חוזר של ההרצה ממתין.

המחלקה הזו נבחרת כששגיאת ה-409 כוללת `executionId`. שגיאת 409 שלא מציינת אף הרצה נשארת `NodaroError` רגילה.

### AppRunVersionChangedError
HTTP 409 עם הקוד `app_run_version_changed`. ניסיון חוזר של הרצת אפליקציה שנכשלה, עם [`client.apps.resume()`](https://nodaro.ai/docs/developers/sdk/apps-and-templates#appsresumeslug-runid-opts), נדחה, כי האפליקציה נמצאת בגרסה אחרת מזו שההרצה הריצה. שום דבר לא נכתב, ולא נגבו קרדיטים.

- השדה `runVersion`: הגרסה שההרצה הריצה.
- השדה `appVersion`: הגרסה שאתם נמצאים בה, כלומר ה-`version` ששלחתם, או, אם לא שלחתם, הגרסה האחרונה של האפליקציה.

כשהתשובה לא כוללת אותם, שני השדות הם `undefined`. כדי להריץ את הגרסה הנוכחית של האפליקציה, התחילו את ההרצה מחדש עם `client.apps.run()`. כדי להמשיך בגרסה של ההרצה עצמה, קראו שוב ל-`resume` עם `{ version: err.runVersion }`.

מיקומים ואובייקטים משתמשים בקוד התנגשות משלהם, `concurrent_modification`, שמגיע כ-`NodaroError` רגילה. טפלו בו באותה דרך: טענו את הפריט מחדש, מזגו ונסו שוב.

## אצוות של Studio
שלוש המחלקות האלה שייכות למתודות של [הפקות Studio](https://nodaro.ai/docs/developers/sdk/studio).

- **`StudioOpError`**: אצווה של פעולות נדחתה, והשדה `opIndex` הוא המיקום של הפעולה שגרמה לכך, כשהספירה מתחילה מ-0. שום דבר מהאצווה לא נכתב. תקנו את הפעולה הזו ושלחו שוב את כל האצווה.
- **`StudioPreviewUnavailable`**: ביקשתם תצוגה מקדימה עם `dryRun: true`, והפריסה הזו לא יכולה לתת אותה. האצווה שלכם לא נשלחה. אמרו למשתמש שתצוגה מקדימה לא זמינה, ואל תחילו את האצווה בלי לשאול אותו.
- **`StudioPreviewAppliedError`**: ביקשתם תצוגה מקדימה, והאצווה הוחלה בכל זאת. התייחסו ל-`applied.production` ול-`applied.version` כמצב הנוכחי. אל תשלחו את האצווה שוב. כש-`applied` הוא `undefined`, טענו את ההפקה מחדש לפני שאתם מחליטים משהו.

## קודים שעשויים להגיע ב-NodaroError
הקודים האלה מגיעים ב-`NodaroError` רגילה. כדי לטפל בהם, השוו את `err.code`.

| סטטוס | קוד | איפה | משמעות |
| --- | --- | --- | --- |
| 400 | `validation_error` | מתודות רבות | שדה חסר או לא תקין. השדה `message` מציין איזה שדה. |
| 400 | `no_valid_inputs` | `client.reduce.run()` | כל הקלטים היו ריקים. |
| 400 | `invalid_edl` | `client.edit.applyEdl()` | רשימת העריכה לא עברה את בדיקת התקינות. |
| 400 | `limit_reached` | `client.developerApps.create()` | כבר הגעתם למספר המרבי של יישומי מפתחים. |
| 400 | `locked_field` | `client.apps.run()` | דריסה ניסתה לשנות יעד, כגון כתובת Webhook. |
| 409 | `name_taken` | דמויות, ארגונים | השם או ה-slug כבר תפוסים. |
| 409 | `concurrent_modification` | מיקומים, אובייקטים | הפריט השתנה מאז שטענתם אותו. |
| 410 | `voice_cloning_retired` | `client.voices.createClone()` | שכפול קול כבר לא מוצע. במקום זאת, עצבו קול. |
| 400 | `quote_unsupported` | `client.nodes.quote()` | הפלטפורמה לא יכולה לתת אומדן מחיר לסוג הרכיב הזה. ההודעה מפרטת את הסוגים שאפשר לקבל עליהם אומדן מחיר. |
| 400, 502 | `advanced_mode_unavailable` | `client.llm.structured()` | הספק של המודל עצמו לא יכול לקבל כרגע בקשות במצב מתקדם. כבו את המצב המתקדם כדי להריץ את המודל. |
| 409 | `job_cancelled` | בקשות LLM שמחויבות לפי השימוש, כגון `client.llm.structured()` | המשימה בוטלה אחרי שהקרדיטים שלה שוריינו, אבל לפני שהקריאה שלה נשלחה. לא נגבים קרדיטים. אל תנסו אותה שוב כמו שהיא: המשימה בוטלה בכוונה. |
| 503 | `job_start_failed` | בקשות LLM שמחויבות לפי השימוש | השרת לא הצליח לרשום שהקריאה מתחילה, ולכן הוא לא שלח אותה. לא נגבים קרדיטים. נסו שוב. |
| 503 | `provider_unavailable` | `client.llm.structuredJob()` | המופע לא יכול להריץ את המודל הזה. אל תנסו שוב. |
| 503 | `feature_disabled` | `client.copilot` | היכולת כבויה בפריסה הזו. |
| 503 | `nodaro_connection_required` | `client.edit.editPlan()` | התקנה באירוח עצמי צריכה לשם כך חיבור ל-Nodaro Cloud. |

כל דף במדריך מפרט את הקודים של המתודות שלו. דף ה-API [שגיאות](https://nodaro.ai/docs/developers/api/errors) מפרט כל קוד שה-API יכול לשלוח.

## ניסיון חוזר בבטחה
- **נסו שוב בקשות קריאה בלי חשש.** ל-`get` או ל-`list` אין תופעות לוואי.
- **אל תחזרו על בקשה בתשלום בלי לבדוק.** ייתכן שבקשה שחרגה מפסק הזמן כבר התחילה הרצה. לפני שאתם מנסים שוב יצירה, העבירו מפתח אידמפוטנטיות: המתודות `client.nodes.run(type, params, { idempotencyKey })` ו-`runAndWait` מקבלות מפתח כזה. כשאתם מנסים שוב את אותה בקשה, שלחו שוב את אותו מפתח, והפלטפורמה תחזיר את ההרצה הראשונה במקום להתחיל הרצה שנייה ולגבות עליה קרדיטים.
- **Studio ו-Recast** משתמשים באסימוני ניסיון חוזר משלהם: הפרמטר `clientRequestId` במתודות של Studio והפרמטר `requestId` ב-`client.recast.rescore()`.
- **נסו שוב אחרי תשובות 5xx, עם הפסקה.** פונקציית `fetch` משלכם ב-[`createClient`](https://nodaro.ai/docs/developers/sdk/client#timeouts-and-a-custom-fetch) היא מקום טוב ללוגיקה הזו.

## throwFromResponse(status, body)‎
```ts
throwFromResponse(status: number, body: {
error?: { code?: string; message?: string; [key: string]: unknown }
}): never
```

ממפה סטטוס HTTP וגוף שגיאה של Nodaro למחלקת השגיאה המתאימה, וזורקת אותה. ה-SDK משתמש בה בכל תגובה, והיא מיוצאת בשביל מימושי תעבורה משלכם שקוראים ל-API בלי הלקוח.

<TypeTable
type={{
status: { type: 'number', required: true, description: "סטטוס ה-HTTP של התגובה." },
body: { type: '{ error?: { code?, message?, ... } }', required: true, description: "גוף ה-JSON אחרי פענוח. שדות נוספים, כגון missingScope, required, available, limitBytes ו-opIndex, ממלאים את השדות המתאימים של השגיאה." },
}}
/>

```ts

throwFromResponse(403, {
error: { code: "insufficient_scope", message: "Missing scope", missingScope: "workflows:execute" },
})
// throws a ForbiddenError whose missingScope is "workflows:execute"
```

רוב הנתיבים עונים עם המעטפת `{ error: { code, message } }`. כמה נתיבים ישנים יותר עונים במבנים אחרים, וה-SDK מפענח גם אותם, כך שגם בשגיאות שלהם יש `code` ו-`message` אמיתיים:

| גוף | `code` | `message` |
| --- | --- | --- |
| `{ error: "<code>", message? }`, למשל מאימון דמויות | המחרוזת | `message`, או הקוד כשאין `message` |
| `{ error: "<code>", error_description }`, מ-OAuth | המחרוזת | `error_description` |
| `{ message, issues: [...] }`, תשובה על קלט לא תקין עם HTTP 400 | `validation_error` | `message` |
| `{ statusCode, error: "<reason phrase>", message }`, למשל נתיב שלא קיים בשרת | `internal_error` | `message` |

ב-HTTP 500 ומעלה, ה-SDK לוקח מהמבנים האלה רק את **הקוד**, אף פעם לא את הטקסט. השדה `message` של שגיאה כזו הוא הקוד שלה, או "Request failed" (הבקשה נכשלה). השדה `message` של המעטפת עצמה נלקח בכל סטטוס.

מחלקת השגיאה עדיין נקבעת לפי הסטטוס: שגיאת 404 היא `NotFoundError`, לא משנה מה כתוב בגוף. כשאין בגוף טקסט קריא, נשארת ההודעה שהמחלקה מגדירה כברירת מחדל, כגון "Not found" (לא נמצא). המחלקה `InsufficientCreditsError` קוראת את `required` ואת `available` מהמעטפת, או את השדות `required` ו-`balance` שהשרת שולח לצידה. במכסה לכל משתמש, הערך של `available` נלקח מ-`remaining`.

## שאלות נפוצות

### איך תופסים שגיאות של ה-API של Nodaro ב-TypeScript?

עטפו את הקריאה ב-try/catch ובדקו את השגיאה עם instanceof, מהמחלקה הספציפית ביותר ועד NodaroError בסוף. כל המחלקות מיוצאות מ-‎@nodaro/sdk.

### מה קורה כשאין בחשבון מספיק קרדיטים?

הקריאה זורקת InsufficientCreditsError עם סטטוס HTTP 402, לפני שעבודה כלשהי מתחילה. השדות required ו-available של השגיאה מראים כמה קרדיטים ההרצה צריכה וכמה קרדיטים יש בחשבון.

### האם שגיאת JobTimeoutError מבטלת את המשימה?

לא. המשימה ממשיכה לרוץ, ובדרך כלל גם מסתיימת. בדקו אותה מאוחר יותר עם client.jobs.get(jobId)‎, או הגדילו את maxMs למודלים איטיים.

### האם כדאי לנסות שוב אחרי שגיאת RateLimitedError?

כן, אחרי הפסקה. המתינו כמה שניות, הכפילו את זמן ההמתנה אחרי כל תשובת 429 חדשה, והפסיקו אחרי כמה ניסיונות.

### למה שגיאת 403 לא מציינת את קוד הסיבה המדויק?

רוב תשובות ה-403 מגיעות כ-ForbiddenError עם הקוד forbidden. את הסיבה קוראים ב-message, ואת ההרשאה החסרה ב-missingScope, כשחסרה הרשאת OAuth. מודל שנדחה הוא היוצא מן הכלל: השגיאה עליו מגיעה כ-ModelUnavailableError, עם קוד משלה.
