# מודלים וקרדיטים

> רשימת מודלי ה-AI שמופע של Nodaro מציע, קריאת יתרת הקרדיטים, בדיקת המחיר של מודל לפני ההרצה וקריאת התוכניות והמנוי שלכם.

Source: https://nodaro.ai/he/docs/developers/sdk/models-and-credits

**`client.models`** מחזיר את קטלוג מודלי ה-AI שמופע של Nodaro מציע, והמשאב **`client.credits`** מחזיר את יתרת הקרדיטים שלכם ואת המחיר של כל מודל. המשאב **`client.billing`** מחזיר את התוכניות שנמכרות ואת המנוי שלכם. השתמשו בהם יחד כדי להראות למשתמשים אילו מודלים הם יכולים לבחור, וכמה עולה כל הרצה. המתודות קוראות לנקודות הקצה שבדף ה-API [קרדיטים](https://nodaro.ai/docs/developers/api/credits). מחירים ויתרות קיימים ב-Nodaro Cloud; בהתקנות באירוח עצמי של מהדורת Community ושל מהדורת Business אין מערכת קרדיטים. ראו את הדף [קרדיטים](https://nodaro.ai/docs/concepts/credits).

## מתודות
| מתודה | מה היא עושה |
| --- | --- |
| [`models.list(opts?)`](#modelslistopts) | רשימת המודלים, מקובצים לפי סוג ולפי יצרן |
| [`credits.balance()`](#creditsbalance) | קריאת יתרת הקרדיטים שלכם והרמה שלכם |
| [`credits.modelCosts(ids)`](#creditsmodelcostsids) | בדיקת המחיר בקרדיטים של עד 50 מודלים או וריאנטים |
| [`credits.transactions(options?)`](#creditstransactionsoptions) | קריאת תנועות הקרדיטים שלכם, עמוד אחד בכל פעם |
| [`credits.usageSummary(options?)`](#creditsusagesummaryoptions) | סיכום השימוש שלכם בקרדיטים בתקופה מסוימת, בשביל מסך חיוב |
| [`credits.history(options?)`](#creditshistoryoptions) | קריאת כל תנועה ביתרה שלכם, עמוד אחד בכל פעם |
| [`credits.exportHistory(filters?)`](#creditsexporthistoryfilters) | ייצוא כל ההיסטוריה המסוננת כקובץ CSV |
| [`credits.claimWelcomeOffer(input?)`](#creditsclaimwelcomeofferinput) | מימוש הטבת ההצטרפות באפליקציית ווב של צד ראשון |
| [`credits.markWelcomeOfferSeen()`](#creditsmarkwelcomeofferseen) | רישום שהחלון הקופץ של הטבת ההצטרפות הוצג |
| [`billing.plans()`](#clientbilling) ועוד | קריאת התוכניות וחבילות הקרדיטים שנמכרות, המנוי שלכם ואופן החיוב של הפריסה. באפליקציית צד ראשון, גם הרכישות, החשבוניות, הכרטיס, הטעינה האוטומטית והתשלום |

## client.models
### models.list(opts?)‎
מחזירה את קטלוג המודלים (`GET /v1/models`), מקובץ לפי סוג (תמונה, וידאו, אודיו) ולפי יצרן. לכל מודל מופיעים היכולות שלו, המחירים שלו בקרדיטים ב-Nodaro Cloud ועצות פרומפט קצרות. נקודת הקצה ציבורית, והשרת שומר את התשובה במטמון למשך 5 דקות. כלי ה-MCP `list_models` מחזיר את אותם נתונים.

```ts
list(opts?: {
kind?: "image" | "video" | "audio"
mode?: string
family?: string
featuredOnly?: boolean
}): Promise<ModelsListResult>
```

<TypeTable
type={{
kind: { type: '"image" | "video" | "audio"', description: "רק מודלים מהסוג הזה." },
mode: { type: 'string', description: "רק מודלים עם המצב הזה, כגון t2i (טקסט לתמונה), t2v (טקסט לווידאו) או i2v (תמונה לווידאו)." },
family: { type: 'string', description: "רק מודלים של היצרן הזה, כגון Google." },
featuredOnly: { type: 'boolean', default: 'false', description: "רק המודלים המומלצים." },
}}
/>

```ts
const catalog = await client.models.list({ kind: "video", mode: "i2v" })

for (const section of catalog.sections) {
for (const family of section.families) {
for (const model of family.models) {
console.log(family.family, model.id, model.durations, model.pricing?.[0]?.credits)
}
}
}
```

התוצאה כוללת את השדה `sections`, עם מקטע אחד לכל סוג ובכל מקטע `families` של מודלים; את השדה `recommendations`, רשימות של מזהי מודלים לשימושים נפוצים; ואת השדה `totalModels`. לכל מודל יש השדות האלה:

| שדה | סוג | תיאור |
| --- | --- | --- |
| `id` | `string` | מזהה המודל, לפרמטר `provider` בהרצה של רכיב. |
| `label`, `description` | `string` | שם התצוגה ותיאור קצר. |
| `modes` | `string[]` | מה המודל עושה, כגון `t2i`, `i2i`, `t2v` או `i2v`. |
| `useCases` | `string[]` | שימושים שהמודל מתאים להם. |
| `aspectRatios`, `resolutions`, `qualities`, `durations` | מערכים | הערכים שהמודל מקבל, איפה שהם רלוונטיים. |
| `features` | `string[]` | יכולות נוספות. |
| `pricing` | `{ identifier, credits, note? }[]` | המחיר בקרדיטים של כל וריאנט: המחיר שלפיו הרצה מחויבת, אותו מספר שמופיע על הכפתור **הרצה**. ב-Nodaro Cloud בלבד. |
| `featured` | `boolean` | האם המודל מומלץ. |
| `promptTips` | `string[]` | עצות קצרות לכתיבת פרומפטים למודל. |
| `doctrineCovered` | `boolean` | הערך הוא `true` רק כשיש ל-Nodaro הכוונה לכתיבת פרומפטים, ממקורות מתועדים, למשפחה של המודל. הציגו תג של ״הכוונת היצרן״ רק כשהערך הוא `true`. |
| `maker` | `{ id, name }` | החברה שיצרה את המודל. השדה `id` קבוע, אחד לכל חברה, כגון `"google"` או `"black-forest-labs"`, כך שאפשר להשתמש בו כמפתח לקבצים הגרפיים שלכם. שרת חדש יותר יכול להוסיף יצרנים, ולכן טפלו גם במזהה שאתם לא מכירים. היצרן הוא לא תמיד ה-`family` שהמודל מקובץ תחתיו. |
| `logoUrl` | `string \| null` | הלוגו הרשמי של היצרן, קובץ SVG ב-CDN המדיה של המופע עצמו. הערך הוא `null` כשהמופע לא מפרסם לוגו ליצרן הזה: הציגו במקומו את שם היצרן או את האות הראשונה שלו. השתמשו בכתובת כמו שהיא, כי לוגו שהוחלף מגיע בכתובת חדשה. כלי ה-MCP משמיט אותו. |

שרת מגרסה ישנה יותר משמיט את `maker` ואת `logoUrl`.

כדי להציג את הלוגואים של היצרנים בלי הקטלוג, קראו ל-`GET /v1/model-makers`. נקודת הקצה מחזירה `{ makers: [{ id, name, logoUrl }] }`, עם כל יצרן שהפלטפורמה מכירה, ועם אותם שדות כמו בקטלוג. היא ציבורית, והתשובה נשמרת במטמון למשך 5 דקות. ראו בדף ה-API [גילוי מודלים](https://nodaro.ai/docs/developers/api/nodes#discover-models).

```ts
for (const section of catalog.sections)
for (const family of section.families)
for (const m of family.models)
console.log(m.id, m.maker?.name ?? family.family, m.logoUrl ?? "(no mark)")
```

אותו קטלוג מופיע בחלק [מודלים](https://nodaro.ai/docs/models) של התיעוד. לעצות על בחירת המודל המתאים, ראו את המדריך [בחירת מודל](https://nodaro.ai/docs/guides/choosing-models).

## client.credits
### credits.balance()‎
מחזירה את יתרת הקרדיטים שלכם ואת הרמה שלכם (`GET /v1/user/credits`). כשאף משתמש לא מחובר, המתודה זורקת `UnauthorizedError`.

```ts
balance(): Promise<UserBalance>
```

```ts
const balance = await client.credits.balance()
console.log(`${balance.total} credits (${balance.effectiveTier})`)
```

| שדה | סוג | תיאור |
| --- | --- | --- |
| `total` | `number` | הקרדיטים שאפשר להוציא עכשיו. |
| `subscription` | `number` | קרדיטי המנוי שנשארו החודש. בכל חודש הם מתאפסים ל-`monthlyAllocation`, גם בתוכנית שנתית, וקרדיטים שלא הוצאו לא עוברים לחודש הבא. |
| `topup` | `number` | קרדיטים שקניתם בנפרד. |
| `dailySpent` | `number` | הקרדיטים שהוצאו היום. |
| `dailyLimit` | `number \| null` | תקרת ההוצאה היומית, או `null` כשאין תקרה. |
| `monthlyAllocation` | `number` | הקרדיטים שהתוכנית מעניקה בכל חודש, גם בחיוב חודשי וגם בחיוב שנתי. |
| `tier` | `string` | רמת המנוי השמורה, כגון `"free"` או `"pro"`. |
| `effectiveTier` | `string` | הרמה שחלה בפועל. הערך `"payg"` פירושו תשלום לפי שימוש: אין מנוי, אבל נקנו קרדיטים, ואז כל המודלים פתוחים, אין סימן מים ואין תקרה יומית. |
| `features` | `Record<string, unknown>` | היכולות של הרמה. |
| `periodEnd` | `string \| null` | סוף תקופת החיוב הנוכחית, כתאריך בפורמט ISO 8601. בתוכנית שנתית הוא רחוק שנה, אבל קרדיטי המנוי עדיין מתאפסים בכל חודש. |
| `appCreditsAllowance` | `number` | קרדיטים שהרווחתם מהרצת אפליקציות, במסלול החינמי. |
| `freeGrantState` | `FreeGrantState` | מה מצב מענק ההרשמה החינמי: `"unclaimed"`, `"granted"`, `"withheld"` או `"revoked"`. הערך `"withheld"` פירושו שהחשבון עובד, אבל המענק לא נכנס, ולכן הציגו את שלבי ההפעלה. השדה לא מופיע כשבפריסה אין מענק הרשמה. |
| `freeGrantNotice` | `"browser_unmeasured"` | רק עם `"withheld"`, כשבקשת המענק לא כללה שום סימן מהדפדפן. האפליקציה שלכם יכולה לומר שקרדיטים חינם דורשים דפדפן רגיל וטעינה מחדש של הדף. |
| `welcomeOffer` | `WelcomeOfferState` | מופיע רק כל עוד הטבת ההצטרפות מופעלת בפריסה. כשהשדה לא מופיע, אין חלון קופץ, אין באנר ואין חסימה. ראו את הסעיף [`claimWelcomeOffer()`](#creditsclaimwelcomeofferinput). |
| `externalWallet` | `{ available: number \| null }` | מופיע כשהפריסה משתמשת בארנק חיצוני משותף. הערך `null` פירושו שהסכום לא זמין. אל תציגו את `total` במקומו. ראו את הדף [ארנקים חיצוניים](https://nodaro.ai/docs/developers/external-wallet). |

כשמחליטים מה להציג, עדיף להסתמך על `effectiveTier` ולא על `tier`.

### credits.modelCosts(ids)‎
בודקת בקריאה אחת את המחיר בקרדיטים של מודלים ושל הווריאנטים שלהם (`POST /v1/credits/model-costs`). המתודה בודקת עד 50 המזהים הראשונים.

```ts
modelCosts(ids: string[]): Promise<{
data: Record<string, number>
missing: string[]
errors: string[]
estimates?: string[]
errorCodes?: Record<string, { code: string; message: string }>
}>
```

<TypeTable
type={{
ids: { type: 'string[]', required: true, description: "מזהי מחיר: מזהה מודל, כגון nano-banana-pro, או מזהה מודל עם הווריאנט שלו, כגון nano-banana-pro:4K או seedance-2-fast:8s:720p." },
}}
/>

```ts
const { data, missing } = await client.credits.modelCosts([
"nano-banana-pro",
"nano-banana-pro:4K",
"seedance-2-fast:8s:720p",
])
console.log(data["nano-banana-pro:4K"])
if (missing.length) console.warn("No price for:", missing)
```

- **`data`** ממפה כל מזהה שיש לו מחיר למחיר שלו בקרדיטים.
- **`missing`** מפרט מזהים שאין להם מחיר. הציגו עבורם מקף.
- **`errors`** מפרט מזהים שהבדיקה שלהם נכשלה. שאר המחירים בכל זאת מגיעים.
- **`estimates`** מפרט את המזהים ב-`data` שהמספר שלהם הוא אומדן, ולא מחיר קבוע. אלה מפתחות מחיר של קריאות LLM שמחויבות לפי השימוש. הציגו אותם כ-`≈N`. כשאין כאלה, השדה לא מופיע.
- **`errorCodes`** מסביר למה לכל מזהה ב-`errors` אין מחיר. כרגע הקוד הוא `llm_model_unpriced`: מודל בלי תעריף בשירות שאמור להריץ אותו, ולכן אי אפשר להריץ אותו. כש-`errors` ריק, השדה לא מופיע.

המזהים של כל מודל מופיעים בשדה `pricing` של [`models.list()`](#modelslistopts) ובדף של כל מודל. הגדרות כגון איכות, רזולוציה ואורך משנות את המחיר, ולכן ציינו את הווריאנט שההרצה תשתמש בו. המחיר נבדק שוב כשההרצה מתחילה, ולכן הקריאה הזו רק מציגה אותו מראש.

מודלי דיבור מתומחרים לפי אורך הטקסט. המזהה שלהם לכל 100 תווים, או חלק מהם, הוא `<model>:per-100-chars`, למשל `elevenlabs-v4:per-100-chars`. בהתקנה שלא מתמחרת דיבור לפי אורך, המזהה הזה מופיע ב-`missing`, כך שהתשובה גם מראה לפי איזה כלל השרת גובה.

בקשה מתומחרת כמכלול ומעוגלת פעם אחת. לכן המחיר המדויק של בקשה עם N יחידות של 100 תווים, או חלק מהן, הוא המזהה `<model>:per-100-chars:<N>u`, כש-N נע מ-8 ועד התקרה של המודל לבקשה. מכפלה של N במחיר היחידה יכולה לצאת גבוהה יותר. אותה צורת מזהה נותנת מחיר לכל הרצה שמחויבת לפי יחידה: המזהה `<row>:<N>u` הוא המחיר המדויק של N יחידות של שורת המחיר הזו, למשל `apply-edl:4u`, `trim-video:5u`, `ltx-2.3-pro-retake:per-second:2.5u` או `elevenlabs-dubbing:3u`. להמרה שלמה של רכיב משנה קול Pro יש מזהה `voice-changer-pro:<slot>,<slot>,…`, והפונקציה `voiceChangerProQuoteCreditId(slots)` מ-`@nodaro/shared` בונה אותו. ראו בדף ה-API [מחירים מדויקים להרצות שמתומחרות לפי יחידה](https://nodaro.ai/docs/developers/api/credits#exact-prices-of-runs-priced-per-unit).

לקריאת LLM אין מחיר קבוע. היא מחויבת לפי הטוקנים שהיא השתמשה בהם בפועל, בתעריף של המודל בשירות שהריץ אותה. מפתח המחיר שלה הוא `<feature>:<model>`, ועוד `:direct` כשהקריאה רצה ב-API של הספק עצמו, והפונקציה `llmPriceKey` מ-`@nodaro/shared/llm-models` בונה אותו. התשובה היא אומדן של קריאה טיפוסית:

```ts

const key = llmPriceKey("llm-chat", "claude-fable-5") // "llm-chat:claude-fable-5"
const { data, estimates } = await client.credits.modelCosts([key])
const shown = estimates?.includes(key) ? `~${data[key]}` : String(data[key])
```

מפתח של קריאה שמחויבת לפי השימוש, כשהוא מופיע ב-`missing`, פירושו שהשרת לא מתמחר קריאות LLM לפי מודל, ולכן אל תציגו לו מחיר. הנתיב `GET /v1/credits/model-cost?model=<id>` עונה על מזהה אחד בצורה `{ model, creditCost, estimate?: true }`. ראו בדף ה-API [קריאות LLM](https://nodaro.ai/docs/developers/api/credits#llm-calls), ואת הסעיף [איך מחויבות קריאות LLM](https://nodaro.ai/docs/concepts/credits#llm-calls) בדף קרדיטים.

### credits.transactions(options?)‎
מחזירה עמוד אחד מתנועות הקרדיטים שלכם, מהחדש לישן (`GET /v1/credits/transactions`). ב-Nodaro Cloud בלבד: בהתקנה באירוח עצמי הנתיב מחזיר 404.

```ts
transactions(options?: { limit?: number; cursor?: string }): Promise<CreditTransactionsPage>
```

<TypeTable
type={{
limit: { type: 'number', description: "מספר השורות בעמוד, מ-1 עד 50.", default: '20' },
cursor: { type: 'string', description: "הערך nextCursor מהעמוד הקודם, כדי לקבל את העמוד הבא." },
}}
/>

העמוד הוא `{ data: CreditTransaction[], nextCursor }`. השדה `nextCursor` הוא `null` כשאין עוד שורות.

| שדה | סוג | תיאור |
| --- | --- | --- |
| `id` | `string` | מזהה התנועה. |
| `created_at` | `string` | תאריך בפורמט ISO 8601. |
| `credits_charged` | `number \| null` | כמה ההרצה עלתה, אחרי החיוב הסופי. הערך יכול להיות נמוך מהשריון, הוא `0` כשהקרדיטים המשוריינים הוחזרו, והוא `null` כל עוד השריון עדיין פתוח. |
| `credits_reserved` | `number \| null` | השריון שנלקח כשההרצה התקבלה. הערך הזה אף פעם לא משתנה. |
| `credits_used` | `number \| null` | אותו שריון, בשם המקורי שלו, שנשמר בשביל אינטגרציות קיימות. עדיף להשתמש ב-`credits_charged`. |
| `action` | `string \| null` | המודל או הפעולה שהקרדיטים שולמו עליהם. |
| `status` | `string \| null` | `"reserved"`, `"committed"` או `"refunded"`. |
| `metadata` | `Record<string, unknown>` | פרטי החיוב של ההרצה עצמה. |
| `payer` | `"user" \| "workspace"` | הערך `"workspace"` פירושו שהתשלום ירד מתקציב של כיתה או של צוות, ולא מהיתרה שלכם. |
| `workspaceId` | `string \| null` | סביבת העבודה שמשלמת, כשהשדה `payer` הוא `"workspace"`. |

**איזה סכום לקרוא.** קודם משוריינים קרדיטים להרצה, ואחר כך מגיע החיוב הסופי שלה: נגבה עליה כל השריון או חלק ממנו, והשאר חוזר ליתרה שלכם. כדי לחשב את ההוצאות שלכם, סכמו את `credits_charged`. ראו את דף ה-API [קרדיטים](https://nodaro.ai/docs/developers/api/credits).

```ts
let cursor: string | undefined
let spent = 0
do {
const page = await client.credits.transactions({ limit: 50, cursor })
for (const t of page.data) spent += t.credits_charged ?? 0
cursor = page.nextCursor ?? undefined
} while (cursor)
console.log(`${spent} credits charged`)
```

### credits.usageSummary(options?)‎
מחזירה את השימוש בקרדיטים בתקופה מסוימת, מסוכם כמו שמסך חיוב מציג אותו (`GET /v1/credits/usage-summary`). המתודה סופרת רק את הכסף האישי שלכם: הרצה שסביבת עבודה שילמה עליה לא נכללת. ב-Nodaro Cloud בלבד.

```ts
usageSummary(options?: { from?: string; to?: string; bucket?: "day" | "week" | "month" }): Promise<UsageSummary>
```

<TypeTable
type={{
from: { type: 'string', description: "תחילת התקופה, כולל: חותמת זמן בפורמט ISO 8601, או תאריך בלבד, כגון 2026-10-01 לכל היום הזה לפי UTC.", default: 'לפני 30 ימים' },
to: { type: 'string', description: "סוף התקופה, כולל, באותה צורה. שנה לכל היותר אחרי from, אחרת המתודה זורקת NodaroError עם סטטוס 400.", default: 'עכשיו' },
bucket: { type: '"day" | "week" | "month"', description: "איך overTime מקבץ את התקופה, לפי UTC.", default: '"day"' },
}}
/>

בפריסה שבה חשבון חיוב אחד משלם על כל המשתמשים, רק הסשן של חשבון החיוב עצמו יכול לקרוא למתודה. קריאה עם האסימונים שלו נכשלת עם `ForbiddenError` והקוד `payer_balance_jwt_only`.

| שדה | סוג | תיאור |
| --- | --- | --- |
| `period` | `{ from, to, bucket }` | התקופה, כפי שהשרת קיבל אותה. |
| `totals` | `UsageSummaryTotals` | `{ used, purchased, granted, refunded, runs, avgPerRun }`. פירוט בהמשך. |
| `mostUsed` | `UsageByModel \| null` | המודל עם ההוצאה הגבוהה ביותר, או `null` כשאין הרצות. |
| `byModel` | `UsageByModel[]` | `{ model, provider, credits, runs, share }`, ממוינים לפי ההוצאה. השדה `share` הוא החלק מתוך `used`, בין 0 ל-1. |
| `byCategory` | `UsageByCategory[]` | `{ category, credits, runs }`, כשהשדה `category` הוא `"image"`, `"video"`, `"audio"` או `"other"`. |
| `overTime` | `UsageOverTime[]` | שורה אחת לכל יחידת זמן, כולל יחידות ריקות, מהישן לחדש: `{ bucket, used, purchased, runs }`. השדה `bucket` הוא היום לפי UTC (`"2026-10-26"`), יום שני של השבוע או החודש (`"2026-10"`). יחידת הזמן הראשונה יכולה להיות שבוע חלקי או חודש חלקי. |
| `bySession`, `byWorkflow` | `UsageByGroup[]` | כמה עלה כל סשן או כל תהליך, ממוינים לפי ההוצאה: `{ id, name, tool, credits, runs, models, lastAt }`. השדה `name` הוא `null` כשאין לסשן או לתהליך שם, או כשהוא כבר לא קיים. השדה `tool` מופיע רק בסשן. |
| `truncated` | `boolean` | הערך הוא `true` כששליפת נתונים בשרת הגיעה לתקרה של 5,000 שורות, ולכן המספרים נמוכים מדי. בקשו תקופה קצרה יותר. |
| `partial` | `boolean` | הערך הוא `true` כששליפה משנית של נתונים נכשלה, ולכן קיבוץ מסוים חסר או בלי שמות. הסכומים הכוללים עדיין מלאים. |

ב-`totals`:

- השדה `used` מסכם את החיוב של כל הרצה. הרצה שעדיין רצה נספרת לפי השריון שלה, והרצה שהקרדיטים עליה הוחזרו לא נספרת.
- השדה `purchased` כולל את החבילות, את הטעינות ואת הקצאות המנוי.
- השדה `granted` הוא הקרדיטים החינמיים, פחות מענקים שנלקחו בחזרה.
- השדה `refunded` הוא מה שחזר על הרצה שהקרדיטים עליה הוחזרו, או מ-Stripe. החלק של השריון שמשתחרר כשהחיוב הסופי של הרצה נמוך ממנו הוא לא החזר.
- השדה `runs` סופר את ההרצות שחויבו או שעדיין רצות. השדה `avgPerRun` הוא `used` חלקי `runs`, עם ספרה אחת אחרי הנקודה.

```ts
const s = await client.credits.usageSummary({ bucket: "week" })
console.log(s.totals.used, "credits used;", s.mostUsed?.model, "most used")
for (const w of s.overTime) console.log(w.bucket, w.used)
```

### credits.history(options?)‎
מחזירה עמוד אחד מתוך **כל התנועות ביתרה שלכם**, מהחדש לישן (`GET /v1/credits/history`): הרצות, רכישות, מענקים, החזרים, תיקונים ופקיעות תוקף, כל אחת עם היתרה שאחריה. המתודה `transactions()` מחזירה רק הרצות, ואילו `history()` מחזירה את הדוח המלא שמסך חיוב מציג. ב-Nodaro Cloud בלבד, ובפריסה עם חשבון חיוב אחד חל אותו כלל כמו ב-`usageSummary()`.

```ts
history(options?: CreditHistoryOptions): Promise<CreditHistoryPage>
```

<TypeTable
type={{
limit: { type: 'number', description: "מספר השורות בעמוד, מ-1 עד 100.", default: '50' },
cursor: { type: 'string', description: "הערך nextCursor מהעמוד הקודם. זה ערך אטום: העבירו אותו כמו שהוא." },
kind: { type: 'CreditHistoryKind', description: '"usage", "purchase", "grant", "refund", "app", "adjustment" או "expiry".' },
model: { type: 'string', description: "מזהה מודל, בהתאמה מדויקת: רק ההרצות של המודל הזה." },
from: { type: 'string', description: "חותמת זמן בפורמט ISO 8601, כולל." },
to: { type: 'string', description: "חותמת זמן בפורמט ISO 8601, כולל." },
q: { type: 'string', description: "מילים לחיפוש בתיאור." },
}}
/>

העמוד הוא `{ data: CreditHistoryEntry[], nextCursor }`. בעמוד האחרון השדה `nextCursor` הוא `null`.

| שדה | סוג | תיאור |
| --- | --- | --- |
| `id` | `string` | המזהה של השורה. |
| `at` | `string` | מתי התנועה קרתה, כתאריך בפורמט ISO 8601. |
| `kind` | `CreditHistoryKind` | סוג התנועה. הערך `"app"` מציין קרדיטים שהרווחתם מאפליקציה שפורסמה או ששילמתם על אפליקציה כזו. הערך `"adjustment"` מציין תיקון של מנהל וכל דבר אחר. |
| `source` | `string` | המונח שהרשומה עצמה משתמשת בו, כגון `usage`, `one_time_purchase`, `subscription_renewal`, `signup_grant`, `refund`, `app_earnings` או `expiry`. |
| `amount` | `number` | התנועה, עם סימן: השריון של הרצה שלילי, ורכישה חיובית. |
| `balanceAfter` | `number` | היתרה מיד אחרי התנועה. |
| `pool` | `"subscription" \| "topup" \| "mixed"` | איזה חלק של היתרה השתנה. |
| `description` | `string \| null` | ההערה של הרשומה. |
| `model`, `provider` | `string \| null` | המודל שרץ והספק שלו, בשורה שקשורה למשימה. |
| `charge` | `number \| null` | החיוב של המשימה, רק ב**שורת השריון** שלה, כלומר בשורה של השריון שנלקח כשההרצה התקבלה. הערך הוא החיוב הסופי, השריון כל עוד ההרצה פתוחה, או `0` אחרי שהקרדיטים הוחזרו. בכל שורה אחרת הערך הוא `null`, ולכן סכימה של `charge` סופרת כל משימה פעם אחת. כדי לדעת כמה הוצאתם, בדקו את השדה הזה, לא את `amount`. |
| `status` | `string \| null` | לצד `charge`: `"reserved"`, `"committed"` או `"refunded"`. |
| `job` | `CreditHistoryJob \| null` | בשורה שקשורה למשימה: `{ id, type, status, startedAt, completedAt, durationMs, workflowId, sessionId }`. השדה `type` ושאר השדות הם `null` כשהמשימה כבר לא קיימת. |
| `receiptUrl` | `string \| null` | הקבלה של Stripe, ברכישה שיש לה קבלה. |

```ts
const page = await client.credits.history({ kind: "usage", limit: 50 })
for (const e of page.data) console.log(e.at, e.model, e.charge ?? e.amount, "→", e.balanceAfter)
```

### credits.exportHistory(filters?)‎
מחזירה את כל ההיסטוריה המסוננת כטקסט CSV (`GET /v1/credits/history?format=csv`). יש בה אותם שדות ומסננים כמו ב-`history()`, שורה אחת לכל תנועה, מהחדש לישן, בלי חלוקה לעמודים. יש בה 5,000 שורות לכל היותר, וכשהרשימה נקטעת, השורה האחרונה מציינת זאת: צמצמו את הטווח של `from` ו-`to` כדי לקבל את השאר.

```ts
exportHistory(filters?: CreditHistoryFilters): Promise<string>
```

```ts
const csv = await client.credits.exportHistory({ from: "2026-10-01T00:00:00Z" })
const href = URL.createObjectURL(new Blob([csv], { type: "text/csv" }))
```

### credits.claimWelcomeOffer(input?)‎
רושמת בפעולה אחת את ה״כן״ של המשתמש להטבת ההצטרפות (`POST /v1/credits/welcome-offer/claim`). המתודה רושמת את ההסכמה לקבלת מיילים שיווקיים, ואז מבקשת את מענק ההרשמה החינמי עם ההסכמה הזו. בדיקות השימוש לרעה עדיין מכריעות, ולכן בדקו את `grant`. בחשבון שהמענק שלו כבר הוכרע, רק ההסכמה נרשמת. ב-Nodaro Cloud בלבד: בהתקנה באירוח עצמי הנתיב מחזיר 404.

```ts
claimWelcomeOffer(input?: WelcomeOfferClaimInput): Promise<WelcomeOfferClaimResult>
```

**המתודה הזו דורשת סשן מחובר בדפדפן**, מ-[`supabaseAuth()`](https://nodaro.ai/docs/developers/sdk/auth#supabaseauthsupabase), כי ההסכמה היא בחירה של אדם. קריאה עם אסימון API או עם אסימון של אפליקציית OAuth נדחית עם `403`. כל עוד ההטבה כבויה בפריסה, הקריאה נדחית עם `409 welcome_offer_disabled`, ולכן הציגו את ההטבה רק כש-`balance().welcomeOffer` קיים.

הטיפוס `WelcomeOfferClaimInput` הוא האובייקט `SignupKeys` שהפונקציה `collectSignupKeys()` מחזירה, ועוד `sourceApp`, מזהה קצר (slug) של האפליקציה שלכם, עד 40 אותיות קטנות, ספרות ומקפים. מזהה פגום נשמר כאילו לא נשלח, ואף פעם לא נדחה. הקלט מקבל גם `locale`, שפת הממשק שבה המשתמש ענה. זה המזהה של שפה ש-Nodaro מציע, כגון `"en"`, `"ja"` או `"pt-BR"`. ההתאמה מדויקת ותלויה ברישיות, ולכן `"en-US"` או `"pt-br"` נשמרים כאילו לא נשלחו. השפה נרשמת עם ההסכמה, כשפה של ההסכמה האחרונה.

| שדה | סוג | תיאור |
| --- | --- | --- |
| `consent` | `"granted"` | ההסכמה לקבלת מיילים נרשמה. |
| `grant` | `FreeGrantState` | הערך `"granted"`: המענק נכנס עכשיו או קודם. הערך `"withheld"`: בדיקות השימוש לרעה דחו אותו. הערך `"revoked"`: המענק נלקח בחזרה בעבר. הערך `"unclaimed"`: שום דבר לא זז, ולכן התייחסו לזה ככישלון ואפשרו למשתמש לנסות שוב. |
| `credits` | `number` | מספר הקרדיטים של המענק, אותו מספר כמו ב-`balance().welcomeOffer.credits`. מענק משלים את קרדיטי המנוי עד המספר הזה. הוא לא מתווסף עליהם. |

לטיפוס `WelcomeOfferState` יש שלושה שדות: `popupSeen` (החלון הקופץ החד־פעמי הוצג), `consentPending` (הקרדיטים כבר נמצאים בחשבון, אבל ההסכמה לקבלת מיילים עדיין חסרה) ו-`credits` (מה שמימוש ההטבה מעניק). הציגו בהטבה את המספר שב-`credits` במקום מספר קבוע, כי המספר נקבע לפי השרת.

### credits.markWelcomeOfferSeen()‎
רושמת שהחלון הקופץ החד־פעמי של הטבת ההצטרפות הוצג (`POST /v1/credits/welcome-offer/seen`), כך שהשדה `welcomeOffer.popupSeen` הופך ל-`true` בכל אפליקציה. קריאה נוספת לא משנה כלום. קריאה שנכשלה פירושה רק שהחלון הקופץ עשוי להופיע פעם נוספת, ולכן אפשר להתעלם משגיאות. חלים אותם כללים כמו ב-`claimWelcomeOffer()`.

```ts
markWelcomeOfferSeen(): Promise<{ ok: true }>
```

```ts

const { welcomeOffer } = await client.credits.balance()
if (welcomeOffer && !welcomeOffer.popupSeen) {
showPopup({ credits: welcomeOffer.credits }) // the server's figure, never a constant
void client.credits.markWelcomeOfferSeen().catch(() => {})
}

// On "Yes":
const keys = await collectSignupKeys()
const { grant, credits } = await client.credits.claimWelcomeOffer({ ...keys, sourceApp: "my-app", locale: "en" })
if (grant === "granted") showToast(`Your ${credits} free credits are ready`)
```

## client.billing
**`client.billing`** קורא את התוכניות והמחירים שנמכרים, את המנוי של מי שקורא למתודות ואת אופן החיוב של הפריסה. באפליקציית צד ראשון שמחוברת עם הסשן של המשתמש עצמו (`supabaseAuth`), המשאב מכסה גם את כל מסך החיוב: רכישות, חשבוניות, הכרטיס השמור, הטעינה האוטומטית והמעבר ל-Stripe Checkout ול-Customer Portal. המתודות שמסומנות **לסשן בלבד** עונות רק לסשן מחובר. קריאה עם מפתח API או עם אסימון OAuth של אפליקציה נכשלת עם `ForbiddenError`, ולכן בלקוח כזה הוסיפו במקום זאת קישור לדף החיוב של האפליקציה.

```ts
// A pricing screen: works signed out.
const { data: plans, topUps } = await client.billing.plans()

// Mark the plan the signed-in account is on.
const { effectiveTier } = await client.billing.subscription()
const current = plans.find((p) => p.id === effectiveTier) // undefined: pay-as-you-go, or a tier not on sale
```

| מתודה | מה חוזר | הערות |
| --- | --- | --- |
| `plans()` | `Promise<BillingPlansResult>` | `GET /v1/billing/plans`. **ציבורית**: לא צריך פרטי גישה, וכל מי שקורא לה מקבל את אותה תשובה. מחזירה `{ data: BillingPlan[], topUps, topUpRange }`. ב-Nodaro Cloud בלבד: במופע באירוח עצמי היא זורקת `NotFoundError`. בפריסה שלא מוכרת למשתמשים שלה היא זורקת `ForbiddenError`. כשאי אפשר לקרוא את המחירים לרגע, היא זורקת `NodaroError` עם סטטוס 503. |
| `subscription()` | `Promise<BillingSubscriptionResult>` | `GET /v1/billing/subscription`. מחזירה `{ data: BillingSubscription \| null, effectiveTier }`. השדה `data` הוא `null` כשלחשבון אף פעם לא היה מנוי. השדה `effectiveTier` הוא הרמה שלפיה נקבע מה החשבון מקבל: בדרך כלל `id` של תוכנית, `"payg"` לקרדיטים שנקנו בלי מנוי, או רמה שלא נמכרת, כגון `"enterprise"`. דורשת סשן בדפדפן, אסימון API אישי בלי מגבלת תהליכים או אסימון OAuth של אפליקציה. ב-Nodaro Cloud בלבד. |
| `surface()` | `Promise<BillingSurface>` | `GET /v1/billing/surface`. ציבורית, בכל המהדורות. אם הפריסה מודדת שימוש בכלל (השדה `providerId` הוא `"none"` כשהיא לא מודדת), ובאיזו יחידה מוצגות העלויות (`displayUnit`). |
| `account()` | `Promise<BillingAccountSummary \| null>` | `GET /v1/billing/account`. השדות `plan`, `balance` ו-`dailyAllowance` של מי שקורא לה, כפי שספק החיוב של הפריסה מדווח עליהם, ביחידה שב-`unit`. הערך הוא `null` כשהספק לא הצליח לענות, וזו לא יתרה של אפס. בכל המהדורות. |
| `purchases()` | `Promise<BillingPurchase[]>` | `GET /v1/billing/transactions`. **לסשן בלבד.** רכישות הקרדיטים של החשבון, מהחדש לישן, 50 האחרונות: `{ id, stripe_transaction_id, type, amount_usd, credits_granted, tier, created_at, receipt_url }`. השדה `type` הוא `"subscription"`, `"topup"` או `"refund"`, וב-`"refund"` הערך של `credits_granted` שלילי. |
| `invoices()` | `Promise<BillingDocument[]>` | `GET /v1/billing/invoices`. **לסשן בלבד.** כל תשלום כמסמך, מהחדש לישן. השדה `kind` הוא `"invoice"` בתשלום על מנוי, עם ה-`number`, ה-`pdfUrl` וה-`url` של Stripe. הוא `"receipt"` בחבילת קרדיטים או בטעינה, עם ה-`credits` שהוענקו ועם הקבלה ב-`url`, בלי PDF. החזר הוא קבלה עם `status` בערך `"refunded"`, וה-`amount` וה-`credits` שלה שליליים. השדה `status` הוא `"paid"`, `"open"`, `"void"`, `"uncollectible"` או `"refunded"`. |
| `paymentMethod()` | `Promise<BillingPaymentMethod>` | `GET /v1/billing/payment-method`. **לסשן בלבד.** `{ card, billingDetails }`: השדה `card` הוא `{ brand, last4, expMonth, expYear }` או `null`, והשדה `billingDetails` הוא `{ name, email, country }` או `null`. בחשבון שעוד לא קנה דבר שניהם `null`, ואף פעם לא חוזרת שגיאה. |
| `autoRecharge()` | `Promise<BillingAutoRecharge>` | `GET /v1/billing/auto-recharge`. **לסשן בלבד.** `{ enabled, thresholdCredits, amountUsd, failureCount, lastAttemptAt, hasSavedCard }`. כשהיתרה יורדת מתחת לסף, הסכום נטען מהכרטיס השמור. |
| `setAutoRecharge(input)` | `Promise<BillingAutoRechargeSaved>` | `PUT /v1/billing/auto-recharge`. **לסשן בלבד.** `{ enabled, thresholdCredits?, amountUsd? }`. כדי להפעיל את הטעינה האוטומטית צריך את שני המספרים: השדה `thresholdCredits`, מ-100 עד 100,000, והשדה `amountUsd`, סכום שלם בתוך `topUpRange`. אחרת המתודה זורקת `NodaroError` עם סטטוס 400. השמירה מאפסת את מונה הכישלונות. |
| `checkout(input)` | `Promise<BillingCheckoutSession>` | `POST /v1/billing/create-checkout-session`. **לסשן בלבד.** עם הקלט `{ priceId, mode?, returnPath?, embedded? }`, המתודה מחזירה `{ url }`, דף של Stripe Checkout למחיר של תוכנית או לחבילת קרדיטים. שלחו את המשתמש לשם. קודי הנחה חלים על תוכניות בלבד, ורכישת חבילה שהחיוב הסופי שלה הוא 0 לא מעניקה קרדיטים. לתוכנית, כל עוד יש לחשבון מנוי, המתודה זורקת שגיאה עם סטטוס 409: השתמשו במקום זאת במתודה `changePlan()`. בפריסה שלא מוכרת למשתמשים שלה, המתודה זורקת `ForbiddenError` עם הקוד `self_serve_disabled`. |
| `loadCredits(input)` | `Promise<BillingLoadSession>` | `POST /v1/billing/create-load-session`. **לסשן בלבד.** עם הקלט `{ amountUsd, returnPath?, embedded? }`, המתודה מחזירה `{ url, credits }`: דף Checkout שטוען סכום שלם בתוך `topUpRange` כקרדיטים, באותו תעריף כמו החבילות. השדה `credits` הוא מה שהטעינה מעניקה. הכרטיס נשמר בשביל הטעינה האוטומטית. |
| `portal(input?)` | `Promise<BillingPortalSession>` | `POST /v1/billing/manage-subscription`. **לסשן בלבד.** עם הקלט `{ returnPath? }`, המתודה מחזירה `{ url }`, דף של Stripe Customer Portal לניהול הכרטיס, כתובת החיוב, החשבוניות וביטול המנוי. לפני הרכישה הראשונה, המתודה זורקת `NotFoundError`. |
| `changePlan(input)` | `Promise<BillingChangePlanResult>` | `POST /v1/billing/change-plan`. **לסשן בלבד.** עם הקלט `{ newPriceId }`, המתודה מחזירה `{ subscriptionId, tier }`. המתודה מעבירה את המנוי הפעיל למחיר של תוכנית אחרת. בשדרוג, הפרש המחיר על יתרת תקופת החיוב נגבה מיד, והשדרוג נכנס לתוקף רק כשהתשלום הזה מצליח. השדרוג מוסיף את ההפרש בין הקרדיטים החודשיים של שתי התוכניות, פעם אחת בכל תקופת חיוב, וחשבונית שדרוג שהחיוב הסופי שלה הוא 0 לא מוסיפה קרדיטים. מעבר לתוכנית נמוכה יותר נכנס לתוקף מיד: ערך הזמן שנותר מקוזז מהחשבונית הבאה, והקרדיטים הנוכחיים נשארים עד סוף התקופה. מעבר מחודשי לשנתי, או בחזרה, לא מוסיף קרדיטים. בלי מנוי פעיל המתודה זורקת `NotFoundError`, עם מחיר של חבילה או עם המחיר הנוכחי שגיאה עם סטטוס 400, וכשהמנוי לא פעיל שגיאה עם סטטוס 409. כשהשדרוג מחכה לתשלום שלו, המתודה זורקת `NodaroError` עם סטטוס 409 והקוד `plan_change_payment_required`: התוכנית לא משתנה, והשדה `details.invoiceUrl` הוא הדף של Stripe לתשלום החשבונית. אחרי 5 בקשות לשינוי תוכנית מאותו חשבון בתוך 24 שעות, המתודה זורקת `RateLimitedError`. ראו בדף ה-API [שינוי תוכנית](https://nodaro.ai/docs/developers/api/credits#change-plan). |

הפרמטר `returnPath` הוא נתיב ב-origin שלכם, כגון `"/billing"` או `"/settings/credits?tab=history"`, ש-Stripe מחזיר אליו את המשתמש. הנתיב מתחיל ב-`/` אחד, יכול לכלול מחרוזת שאילתה, ואין בו שם מארח או `#`. התשלום והטעינה מוסיפים למחרוזת השאילתה שלו `checkout=success` או `checkout=cancelled`. הפרמטר `returnPath` גובר על `embedded`. בלעדיו, משתמשים בדפים של האפליקציה של Nodaro עצמה.

```ts
// A billing screen in a first-party app, signed in with supabaseAuth.
const [method, docs, auto] = await Promise.all([
client.billing.paymentMethod(),
client.billing.invoices(),
client.billing.autoRecharge(),
])
// Buy a pack and come back to this screen.
const { url } = await client.billing.checkout({ priceId: pack.priceId!, returnPath: "/settings/credits" })
window.location.assign(url!)
```

השדות של `BillingPlan`:

| שדה | סוג | תיאור |
| --- | --- | --- |
| `id` | `string` | `"free"`, `"basic"`, `"standard"`, `"pro"` או `"business"`. |
| `name` | `string` | שם התצוגה. |
| `prices` | `{ monthly, yearly } \| null` | הערך הוא `null` במסלול החינמי. השדה `monthly` הוא `{ priceId, amount, currency, credits }`. השדה `yearly` מוסיף את `perMonth`, הסכום השנתי מחולק על פני החודשים שלו. השדה `priceId` הוא מזהה המחיר ב-Stripe שהמתודות `checkout()` ו-`changePlan()` מקבלות, ושהשדה `stripe_price_id` של המנוי תואם לו. השדה `amount` הוא ביחידה הראשית של המטבע (`12` פירושו 12.00), והשדה `currency` הוא קוד ISO 4217, כגון `"USD"`. כל אחד מהמחירים יכול להיות `null` כשאי אפשר למכור אותו כרגע: הציגו אותו כלא זמין, אף פעם לא כחינם. |
| `prices.*.credits` | `{ amount, per } \| null` | מה המחיר מעניק, כשהשדה `per` הוא `"month"` או `"year"`. מחיר שנתי מעניק את הקרדיטים של התוכנית בכל חודש, כמו מחיר חודשי. |
| `signupCredits` | `number \| null` | המענק החד־פעמי של המסלול החינמי בהרשמה. הערך הוא `null` בתוכנית בתשלום. |
| `parallelism` | `number` | כמה רכיבים של תהליך אחד רצים בו־זמנית. |
| `storageBytes` | `number` | נפח האחסון למדיה שלכם, בבייטים. |

השדה `topUps` כולל את חבילות הקרדיטים, `{ id, credits, price, priceId }`, מהקטנה לגדולה. השדה `id` הוא המזהה של החבילה עצמה, כגון `"topup-25"`. השדה `price` הוא `null` כשאי אפשר לציין אותו כרגע. השדה `priceId` הוא מזהה המחיר ב-Stripe בשביל `checkout()`, והוא `null` כשהמחיר `null`. תמיד אפשר לקנות חבילה כטעינה של `price.amount` דרך `loadCredits()`. השדה `topUpRange`, `{ min, max, currency }`, הוא טווח הסכומים השלמים שאפשר לטעון כקרדיטים, באותו תעריף כמו החבילות. על קניית קרדיטים בלי תוכנית, ראו את הדף [API בלי מנוי](https://nodaro.ai/docs/developers/pay-as-you-go).

## הצגת מודלים עם המחירים שלהם
כך בונים תפריט בחירת מודל לרכיב אחד, עם המחיר ליד כל מודל:

```ts
const { data: node } = await client.nodes.get("generate-video")
const { data: prices } = await client.credits.modelCosts(node.providers ?? [])

const options = (node.providers ?? []).map((id) => ({
id,
price: prices[id] ?? null, // null: no price on this instance
}))
```

השמיטו את `provider` בהרצה כדי להשתמש במודל ברירת המחדל של הרכיב. כשמשתמש בוחר מודל, שלחו את המזהה שלו בתור `provider` ל-[`client.nodes.runAndWait()`](https://nodaro.ai/docs/developers/sdk/nodes).

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

### איך מקבלים מקוד את רשימת המודלים ש-Nodaro מציע?

קראו ל-client.models.list()‎. המתודה מחזירה כל מודל תמונה, וידאו ואודיו, מקובץ לפי סוג ולפי יצרן, עם המצבים, יחסי הגובה־רוחב, הרזולוציות והאורכים שלו, וב-Nodaro Cloud גם עם המחירים שלו בקרדיטים.

### איך בודקים את המחיר של מודל אחד לפני ההרצה?

קראו ל-client.credits.modelCosts עם מזהי המחיר של המודל, כגון nano-banana-pro או nano-banana-pro:4K. השדה data ממפה כל מזהה למחיר שלו בקרדיטים.

### איך מקבלים אומדן מחיר לקריאת LLM עם ה-SDK?

בנו את מפתח המחיר עם llmPriceKey(feature, model)‎ מ-‎@nodaro/shared/llm-models, למשל llm-chat:claude-fable-5, והעבירו אותו ל-client.credits.modelCosts. המספר הוא אומדן של קריאה טיפוסית, והוא מופיע ב-estimates, ולכן הציגו אותו כ-≈N.

### איך בודקים את יתרת הקרדיטים עם ה-SDK?

קראו ל-client.credits.balance()‎. השדה total הוא מספר הקרדיטים הזמינים, בחלוקה לקרדיטי מנוי ולקרדיטי טעינה.

### איך מחשבים עם ה-SDK כמה עלו ההרצות שלי?

עברו על העמודים של client.credits.transactions()‎ עם הערך nextCursor של כל עמוד, וסכמו את credits_charged. השריון שנלקח קודם בשביל ההרצה, credits_reserved, הוא לא מה ששילמתם.

### איך מציגים את התוכניות והמחירים של Nodaro באפליקציה שלי?

קראו ל-client.billing.plans()‎. המתודה לא דורשת התחברות, ומחזירה כל תוכנית עם המחיר החודשי והשנתי שלה ועם הקרדיטים שלה, וגם את חבילות הקרדיטים. רכישה דורשת את הסשן המחובר של המשתמש עצמו, דרך client.billing.checkout()‎.

### איך בונים עם ה-SDK מסך של שימוש או של היסטוריית חיוב?

קראו ל-client.credits.usageSummary()‎ כדי לקבל את הסכומים של תקופה, לפי מודל, קטגוריה, סשן ותהליך. קראו ל-client.credits.history()‎ כדי לקבל כל תנועה ביתרה, ול-exportHistory()‎ כדי לקבל את אותה רשימה כקובץ CSV.

### האם להתקנות באירוח עצמי יש מחירים בקרדיטים?

לא. בהתקנות באירוח עצמי של מהדורת Community ושל מהדורת Business אין מערכת קרדיטים, ולכן המחירים של המודלים ועלויות הקרדיטים לא מופיעים בתשובות שלהן.
