# תהליכים

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

Source: https://nodaro.ai/he/docs/developers/api/workflows

**תהליך** הוא קנבס שמור של רכיבים מחוברים, וה-API יכול להריץ אותו, לתת לו ערכי קלט חדשים להרצה אחת ולנהל אותו כמו כל משאב אחר. הרצה מתחילה עם בקשה אחת שמחזירה `executionId`, ואז בודקים את הסטטוס של ההרצה מדי כמה שניות עד שהיא מסתיימת. הרצה קצרה יכולה גם לחכות לתוצאה שלה באותה בקשה.

## נקודות קצה
### הרצת תהליך
| שיטה | נתיב | מה היא עושה |
| --- | --- | --- |
| `POST` | `/v1/workflows/:id/run` | הרצת התהליך השמור, או של חלק מהרכיבים שלו. התגובה היא `202` עם `executionId`. |
| `POST` | `/v1/workflows/:id/render-final/estimate` | אומדן מחיר לרינדור סופי: הרכיבים שהוא מריץ והקרדיטים שלו. שום דבר לא נוצר ולא נגבים קרדיטים. |
| `GET` | `/v1/api/workflows` | רשימת התהליכים שאסימון ה-API שלכם יכול להריץ. תומכת ב-`?limit=` וב-`?cursor=`. |
| `GET` | `/v1/api/schema?workflowId=…` | שדות הקלט והפלטים של תהליך, עם `estimatedCredits`. |
| `POST` | `/v1/api/run` | הרצת תהליך עם ערכי קלט חדשים. הוסיפו `?wait=true&timeout=…` כדי לחכות לתוצאה. |
| `GET` | `/v1/api/status/:execId` | הסטטוס של ההרצה, ספירת הרכיבים והקרדיטים שהוצאו, ואחרי שההרצה הושלמה גם ה-`outcome` שלה. |
| `GET` | `/v1/api/result/:execId` | הפלטים של ההרצה, אחרי שהסטטוס שלה הוא `completed` או `failed`. |
| `POST` | `/v1/app/:slug/run` | הרצת אפליקציה שפורסמה עם שדות הטופס שלה. ראו את הדף [מיני־אפליקציות](https://nodaro.ai/docs/developers/embed/miniapps). |

### ניהול תהליכים
| שיטה | נתיב | מה היא עושה |
| --- | --- | --- |
| `GET` | `/v1/projects/:projectId/workflows` | התהליכים של פרויקט אחד, בלי הרכיבים והחיבורים שלהם. |
| `GET` | `/v1/workflows` | התהליכים שלכם בכל הפרויקטים. |
| `GET` | `/v1/workflows/:id` | תהליך אחד עם הרכיבים, החיבורים וההגדרות שלו. |
| `POST` | `/v1/projects/:projectId/workflows` | יצירת תהליך בפרויקט. |
| `PATCH` | `/v1/workflows/:id` | שינוי של כל צירוף של שדות התהליך. |
| `DELETE` | `/v1/workflows/:id` | מחיקת תהליך. |
| `GET` | `/v1/workflows/:id/export` | ייצוא התהליך כחבילת JSON. הוסיפו `?assets=true` כדי לכלול את הדמויות, האובייקטים והמיקומים שלו. |
| `POST` | `/v1/workflows/import` | יצירת תהליך מחבילה. |
| `POST` | `/v1/workflows/:id/move` | העברת תהליך לפרויקט אחר. |
| `GET` | `/v1/workflows/shared-with-me` | תהליכים שאנשים אחרים שיתפו איתכם. |
| `GET` | `/v1/mcp-conversations` | שיחות ה-MCP שלכם בחודש אחד. ראו את הסעיף [שיחות MCP](#mcp-conversations). |

שיתוף, שותפים והרשאות לכל תהליך מתוארים בדף ה-API [סביבות עבודה וארגונים](https://nodaro.ai/docs/developers/api/workspaces).

## שלוש דרכים להריץ תהליך
| נקודת קצה | פרטי גישה | ערכי קלט | מתי להשתמש בה |
| --- | --- | --- | --- |
| `POST /v1/workflows/:id/run` | כל אסימון. אסימוני OAuth צריכים את `workflows:execute`. | הערכים השמורים. השדה `nodeIds` מריץ חלק מהרכיבים. | מריצים את התהליך כמו שהוא שמור, בשביל החשבון שלכם או בשביל משתמש OAuth. |
| `POST /v1/api/run` | אסימון API אישי | השדה `inputs` מחליף את הערכים של רכיבי הקלט בהרצה הזו. | סקריפט צריך ערכים שונים בכל הרצה, או רוצה לחכות לתוצאה. |
| `POST /v1/app/:slug/run` | כל אסימון | שדות הטופס של האפליקציה, ועוד דריסות גולמיות של רכיבים | התהליך פורסם כאפליקציה עם טופס שהשדות שלו נבחרו בקפידה. |

חמש נקודות הקצה של `/v1/api/` הן מסלול אסימוני ה-API המקורי. הן קיימות מלפני האפליקציות שפורסמו ועדיין נתמכות, אבל אינטגרציות חדשות שצריכות קלטים מפרסמות בדרך כלל את התהליך כאפליקציה ומשתמשות ב-`POST /v1/app/:slug/run`. הנתיב הזה מקבל את השדות של האפליקציה באובייקט `inputs` שטוח, ואובייקט `inputOverrides` אופציונלי של דריסות גולמיות בצורה `{ nodeId: { field: value } }`. השניים ממוזגים, והשדה `inputOverrides` גובר בכל שדה ששניהם קובעים. קלט שטוח שהשדה שלו הוא מספר, כגון מחוון שהאפליקציה חושפת, מקבל מספר או מחרוזת מספרית: הערך `"0.4"` מגיע לרכיב כ-`0.4`. ערך שהוא לא מספר מועבר כמו שהוא, והרכיב מתייחס אליו כמו תמיד.

## הרצת תהליך שמור
הנתיב `POST /v1/workflows/:id/run` מתחיל הרצה של התהליך כמו שהוא שמור. שלחו גוף ריק כדי להריץ את כל הרכיבים, או `nodeIds` כדי להריץ חלק מהם:

**curl**

```bash
curl -s -X POST https://app.nodaro.ai/v1/workflows/8c2d7f1e-3a4b-4c5d-9e6f-7a8b9c0d1e2f/run \
  -H "Authorization: Bearer $NODARO_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{}'
```

```json
{ "executionId": "3f9e2b1a-7c6d-4e5f-8a9b-0c1d2e3f4a5b", "status": "pending" }
```

**TypeScript SDK**

```ts
const { executionId } = await client.workflows.run(workflowId)

// Or run only some nodes:
await client.workflows.run(workflowId, { nodeIds: ['text-prompt-1', 'generate-image-1'] })
```

**CLI**

```bash
nodaro workflows run 8c2d7f1e-3a4b-4c5d-9e6f-7a8b9c0d1e2f --watch
nodaro workflows run 8c2d7f1e-3a4b-4c5d-9e6f-7a8b9c0d1e2f --node text-prompt-1 generate-image-1
```

<TypeTable
type={{
nodeIds: { type: 'string[]', description: 'אופציונלי. המזהים של הרכיבים להרצה. השמיטו אותו כדי להריץ את התהליך כולו.' },
inputOverrides: { type: 'object', description: 'אופציונלי. ערכים לרכיבי קלט בהרצה הזו בלבד, כשהמפתח הוא מזהה הרכיב, למשל ‎{ "text-prompt-1": { "text": "a cat at sunset" } }‎ (חתול בשקיעה). ערך בודד, ולא אובייקט, תחת מזהה של רכיב נכנס לשדה הראשי של הרכיב. תוויות של רכיבים לא מתקבלות כאן.' },
continueFromExecutionId: { type: 'string', description: 'אופציונלי. הרצה שהושלמה של התהליך הזה, שאותה ממשיכים: רק הרכיבים שב-nodeIds רצים, וכל רכיב אחר מעביר הלאה את מה שההרצה ההיא יצרה. ראו את הסעיף ״המשך הרצה״. עם continueMode: הרצה שנכשלה, שממשיכים מהנקודה שבה היא נעצרה.' },
continueMode: { type: '"from-failed"', description: 'אופציונלי. עם continueFromExecutionId, ממשיך הרצה שנכשלה מהנקודה שבה היא נעצרה. nodeIds הופך לאופציונלי. ראו את הסעיף ״המשך הרצה שנכשלה״.' },
renderFinal: { type: '{ renderNodeId: string }', description: 'אופציונלי. רינדור של רכיב החלת רשימת עריכה במצב סופי, ואז הרצה של כל רכיב שאחריו. שלחו אותו עם continueFromExecutionId, ובלי nodeIds או inputOverrides. ראו את הסעיף ״רינדור סופי״.' },
}}
/>

התגובה היא `202 Accepted` עם `{ executionId, status }`, כשהשדה `status` הוא `pending` או `running`. בדקו את הסטטוס של ההרצה מדי כמה שניות עם `GET /v1/workflow-executions/:id`, שמתואר בדף ה-API [הרצות](https://nodaro.ai/docs/developers/api/executions). עם `--watch`, ה-CLI בודק את הסטטוס בשבילכם ויוצא עם קוד `2` כשההרצה נכשלת ועם `130` כשהיא מבוטלת.

| סטטוס | קוד | משמעות |
| --- | --- | --- |
| 402 | `insufficient_credits` | הרצת המשך או רינדור סופי שהקרדיטים שלכם לא מכסים. שום דבר לא נוצר. |
| 403 | `forbidden` | מותר לכם לראות את התהליך, אבל לא להריץ אותו. בסביבת עבודה, הרצה דורשת גישת עריכה וחברות פעילה. |
| 404 | `not_found` | אין תהליך כזה, או שאין לכם גישה לראות אותו. |
| 409 | `already_running` | לתהליך כבר יש הרצה פעילה. התגובה כוללת את ה-`executionId` של ההרצה הזו. |

הרצה רגילה לא בודקת את היתרה שלכם לפני שהיא מתחילה. כשהקרדיטים שלכם לא מכסים רכיב, הרכיב הזה נכשל עם שגיאה של חוסר בקרדיטים, וההרצה מדווחת על הכשל.

### המשך הרצה
הנתיב `POST /v1/workflows/:id/run` יכול להמשיך הרצה קודמת במקום להתחיל מההתחלה. שלחו `continueFromExecutionId` עם המזהה של ההרצה ההיא, יחד עם `nodeIds`. ההרצה מריצה רק את הרכיבים שב-`nodeIds`. כל רכיב אחר מעביר הלאה את מה שההרצה הקודמת יצרה, כמו שההרצה ההיא תיעדה אותו, ולעולם לא את התוצאות השמורות של התהליך. הקלטים שההרצה ההיא השתמשה בהם, כגון העלאה או קלט של אפליקציה, משמשים שוב, ושום רכיב שלפני הרכיבים שב-`nodeIds` לא רץ שוב, ולא משלמים עליו שוב.

- רכיב שגם ההרצה הקודמת לא הריצה מעביר הלאה את התוצאות השמורות שלו, כמו שעשה בהרצה ההיא.
- רכיב [תוכנית עריכה](https://nodaro.ai/docs/nodes/video/edit-plan) מעביר הלאה את התוכנית שההרצה הקודמת יצרה, כשהבדיקה הנוכחית שלכם מוחלת עליה. בדיקה שנעשתה על תוכנית אחרת לא נלקחת בחשבון.
- רכיב שההרצה הקודמת לא השלימה לא מעביר שום דבר ולא רץ שוב. Nodaro מדלג על רכיב שכל הקלטים שלו מגיעים מרכיבים כאלה, ועל הרכיבים שאחריו.
- ה-`inputOverrides` של ההרצה הקודמת מוחלים שוב. ה-`inputOverrides` שאתם שולחים מוחלים מעליהם, שדה אחר שדה, והערכים שלכם גוברים.

דוגמה אחת היא רינדור סופי שבונים ידנית. הוא מריץ את הרינדור ואת כל הרכיבים שאחריו, כשהרינדור במצב סופי בהרצה הזו בלבד:

```json
{
"nodeIds": ["<render node id>", "<every node after it>"],
"inputOverrides": { "<render node id>": { "quality": "final" } },
"continueFromExecutionId": "<a completed run whose render made a Preview>"
}
```

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

| סטטוס | קוד | משמעות |
| --- | --- | --- |
| 400 | `continuation_subset_required` | השדה `nodeIds` חסר או ריק, בלי `continueMode`. |
| 400 | `continuation_workflow_mismatch` | ההרצה הקודמת הריצה תהליך אחר. |
| 400 | `continuation_version_mismatch` | ההרצה הקודמת הריצה גרסת אפליקציה שפורסמה של התהליך. כדי להמשיך הרצת אפליקציה שנכשלה, השתמשו בניסיון החוזר של האפליקציה. |
| 400 | `validation_error` | השדה `continueFromExecutionId` הוא לא מזהה של הרצה. |
| 404 | `continuation_not_found` | אין הרצה כזו, או שההרצה לא שלכם. |
| 409 | `continuation_not_completed` | ההרצה הקודמת עדיין רצה, נכשלה או בוטלה. |

### המשך הרצה שנכשלה
אפשר להמשיך הרצה של תהליך שמור שנכשלה מהנקודה שבה היא נעצרה, ולשמור את מה שהיא השלימה. שלחו `continueFromExecutionId` עם המזהה של ההרצה שנכשלה ו-`"continueMode": "from-failed"`:

```json
{
"continueFromExecutionId": "<the run that failed>",
"continueMode": "from-failed"
}
```

- **רק מה שנשאר רץ.** כל רכיב שההרצה שנכשלה השלימה מעביר הלאה את התוצאה שלו, ולא רץ שוב ולא משלמים עליו שוב. רכיב שנכשל רץ שוב במלואו, כולל כל פריט ברשימה.
- **הרכיבים שנלקחים בחשבון.** השדה `nodeIds` הוא אופציונלי. בלעדיו, ההרצה ממשיכה על הרכיבים שעליהם היא התחילה: הרצה של רכיבים שציינתם ממשיכה על אותם רכיבים, והרצה מלאה על כל הרכיבים. הרצת המשך של הרצת המשך שומרת על הרכיבים של ההרצה הראשונה. כל רכיב שנשאר נכשל או מעוכב מתווסף אליהם, יחד עם מה שהרכיבים האלה צריכים במעלה הזרם.
- **כל רכיב שאתם מציינים נחשב.** עם `nodeIds`, רכיב שאף הרצה בשרשרת לא הריצה אף פעם נחשב כרכיב שנשאר, ולכן הוא רץ ונכלל באומדן המחיר. מכאן שציון של כל הרכיבים מריץ גם את כל הרכיבים שכל ההרצות בשרשרת השאירו בחוץ. רכיב שרץ באחת ההרצות בשרשרת לא ירוץ שוב רק כי ציינתם אותו.
- **רכיבים שאף פעם לא רצים.** רכיבי מקור והעלאה, טריגרים, בוררים וקונטיינרים מסוג קבוצה או איסוף מעבירים הלאה את מה שההרצה העבירה. רכיב שהוגדר לדילוג, בתהליך או ב-`inputOverrides` של ההרצה, אף פעם לא רץ. כשמציינים רק רכיבים כאלה, לא נשאר שום דבר להריץ.
- **שרשראות עם הרצות המשך ישנות.** הרצת המשך שרצה לפני שהכלל הזה נכנס לתוקף תיעדה רכיב כזה כרכיב שהושלם. בשרשרת כזו, גם כשמציינים את הרכיב, הוא עדיין נחשב כרכיב שהסתיים, ולכן הוא לא רץ. כדי להריץ אותו, המשיכו את ההרצה החדשה ביותר בשרשרת עם `nodeIds` בלבד, בלי `continueMode`. זה עובד רק כשההרצה הזו הושלמה. כשהיא נכשלה, המשיכו קודם מנקודת הכשל, בלי הרכיב הזה.
- **הרצת ההמשך החדשה ביותר קובעת.** הרצת המשך כוללת את כל מה שההרצות שלפניה השלימו, ולכן השרת תמיד בונה על הרצת ההמשך החדשה ביותר של ההרצה. כשהרצת ההמשך הזו נכשלה, הבקשה נדחית עם `continuation_superseded`, שמציין אותה: המשיכו אותה במקום זאת. כשהיא כבר הריצה את כל מה שנשאר, אותו קוד אומר שהעבודה הסתיימה. כשהיא השלימה רק חלק ממה שנשאר, ההרצה נבנית עליה. התגובה `202` מציינת ב-`continuedFrom` את ההרצה שממנה ההרצה החדשה ממשיכה.
- **הרצת המשך שנעצרה בתצוגה מקדימה.** הרצת המשך נוספת הייתה נעצרת באותה תצוגה מקדימה, ולכן הבקשה נדחית עם `continuation_preview_pending`. התגובה מציינת את הרצת ההמשך הזו ב-`executionId`, את הרינדורים שלה ב-`renderNodeIds` ואת רכיבי [הידוק פרק](https://nodaro.ai/docs/nodes/video/tighten-episode) שלה ב-`reviewNodeIds`. השדה `next` שבתגובה מתאר את ההרצה שמשלימה אותם: שלחו את `next` כגוף הבקשה של `POST /v1/workflows/:id/run`.
- **היתרה נבדקת קודם.** הרצת המשך שהקרדיטים שלכם לא מכסים נדחית עם `402 insufficient_credits` לפני שהיא מתחילה. האומדן שלה מחושב לפי מה שהיא עדיין מריצה. רכיב שההרצה שנכשלה השלימה אף פעם לא נכלל באומדן. רכיב שציינתם ושאף הרצה בשרשרת שלו לא הריצה אף פעם נכלל באומדן, כי הוא רץ.
- **שלחו כותרת `Idempotency-Key`.** שתי הרצות המשך שנשלחות באותו רגע יכולות להתחיל שתיהן. עם אותו מפתח, ניסיון חוזר של הבקשה מחזיר את ההרצה שכבר התחילה, עם `"deduped": true`.

בקשה להמשיך הרצה שנכשלה נדחית גם עם הקודים האלה, בנוסף לקודים שלמעלה:

| סטטוס | קוד | משמעות |
| --- | --- | --- |
| 409 | `continuation_not_failed` | ההרצה לא נכשלה. |
| 409 | `continuation_superseded` | הרצת המשך חדשה יותר של ההרצה נכשלה (המשיכו אותה במקום זאת), או שהיא השלימה את כל מה שנשאר. היא מצוינת ב-`executionId` וב-`supersededBy: { executionId, status }`. |
| 409 | `continuation_preview_pending` | הרצת ההמשך החדשה ביותר נעצרה בתצוגה מקדימה. שלחו קודם את ה-`next` שלה. |
| 409 | `continuation_subset_required` | לא נשאר מה להמשיך, או שאף אחד מהרכיבים שעליהם ההרצה הראשונה התחילה כבר לא נמצא בתהליך. ציינו את הרכיבים ב-`nodeIds`, או התחילו הרצה חדשה. |
| 409 | `already_running` | הרצה של התהליך הזה, כולל הרצת המשך, עדיין רצה. ה-`executionId` שלה נמצא בתגובה. |

אי אפשר להמשיך כאן הרצה של אפליקציה שפורסמה: השתמשו בניסיון החוזר של האפליקציה, שמתואר בסעיף [ניסיון חוזר של הרצה שנכשלה](https://nodaro.ai/docs/developers/embed/miniapps#retry-a-failed-run) בדף הטמעת מיני־אפליקציה. בעורך, אותה הרצת המשך מתחילה מהכפתור **המשך מנקודת הכשל**, שמתואר בדף [הרצת תהליכים](https://nodaro.ai/docs/concepts/running-workflows#continue-a-run-that-failed).

### רינדור סופי
במקום לפרט את הרכיבים בעצמכם, ציינו את הרינדור ותנו לשרת לחשב את השאר. שלחו `renderFinal` עם מזהה הרכיב של הרינדור ועם `continueFromExecutionId`, ובלי `nodeIds` או `inputOverrides`. השרת מריץ את מה שהכפתור **רינדור סופי** מריץ בעורך: את הרינדור של רכיב [החלת רשימת עריכה](https://nodaro.ai/docs/nodes/video/apply-edl) במצב סופי בהרצה הזו בלבד, ואז את כל הרכיבים שאחריו. כשרכיב [מחליף מצלמות](https://nodaro.ai/docs/nodes/video/camera-switch) נמצא בין רכיב תוכנית עריכה לרינדור, רכיב מחליף מצלמות רץ שוב קודם. רכיב תוכנית עריכה לא רץ שוב. ההרצה היא אז הרצת המשך, עם אותן בדיקות.

```json
{
"renderFinal": { "renderNodeId": "<render node id>" },
"continueFromExecutionId": "<a completed run whose render made a Preview>"
}
```

קבלו קודם אומדן מחיר עם `POST /v1/workflows/:id/render-final/estimate` וגוף בקשה `{ renderNodeId, continueFromExecutionId }`. אומדן המחיר לא יוצר שום דבר ולא גובה קרדיטים. התגובה היא `200` עם `{ data: { renderNodeId, nodeIds, inputOverrides, estimatedCredits, holdCredits, sufficient, available } }`:

- השדות `nodeIds` ו-`inputOverrides` הם הרכיבים שההרצה מריצה והדריסה שהיא רצה איתה, `{ "<render node id>": { "quality": "final" } }`.
- השדה `estimatedCredits` הוא המחיר של הרכיבים שההרצה מריצה, כשהרינדור במצב סופי. כשההרצה מריצה רכיב LLM, התגובה מוסיפה `estimatedCreditsIsEstimate: true`: על הרכיב הזה משלמים לפי השימוש בפועל, ולכן הציגו את הסכום כ-`≈N`.
- השדה `holdCredits` הוא מה שמשוריין להרצה בזמן הרינדור. לרכיב [קליפ UGC](https://nodaro.ai/docs/nodes/video/ugc-clip) משוריינים קרדיטים בערך פי שניים מהמחיר שלו, והחלק שלא נדרש חוזר כשהקליפ מסתיים. הערך `holdCredits` אף פעם לא נמוך מ-`estimatedCredits`.
- השדה `sufficient` אומר אם הקרדיטים שלכם מכסים את השריון הזה, והשדה `available` הוא הקרדיטים שזמינים לכם להוצאה. הערך `available` הוא `null` כשתקציב של סביבת עבודה או חשבון החיוב של פריסה משלמים.
- כל שלושת הסכומים הם `null` במהדורה בלי קרדיטים.

בקשת האומדן נדחית עם הקודים של הרצות ההמשך שלמעלה. שני הנתיבים דוחים בקשות גם עם הקודים האלה:

| סטטוס | קוד | משמעות |
| --- | --- | --- |
| 400 | `render_final_node_not_found` | אין בתהליך רכיב עם המזהה הזה. |
| 400 | `render_final_not_a_render` | הרכיב הוא לא רינדור של רכיב החלת רשימת עריכה. |
| 400 | `validation_error` | השדה `renderFinal` נשלח עם `nodeIds` או `inputOverrides`, או בלי `continueFromExecutionId`. |

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

## הרצת תהליך עם ערכי קלט חדשים
הנתיב `POST /v1/api/run` מקבל אובייקט `inputs` שמחליף את הערכים של רכיבי הקלט בהרצה האחת הזו. האימות בנתיב הזה נעשה עם אסימון API אישי. המפתחות של `inputs` הם מזהי רכיבים, או, לנוחותכם, תוויות ייחודיות של רכיבים. בתוך כל מפתח, ציינו את שדה הקלט של הרכיב, כגון `text` בפרומפט טקסט.

Workflow: התהליך שבדוגמה: רכיב טקסט, שהרצה דרך ה-API מחליפה את הטקסט שלו, מזין רכיב יצירת תמונה.

- טקסט → יצירת תמונה (פרומפט)

הדוגמה שלמטה מריצה תהליך שבו רכיב [טקסט](https://nodaro.ai/docs/nodes/automate/text) עם המזהה `text-prompt-1` מחובר לרכיב [יצירת תמונה](https://nodaro.ai/docs/nodes/image/generate-image).

### מציאת שדות הקלט
הנתיב `GET /v1/api/schema` מפרט את הקלטים של התהליך, עם השדה שכל אחד מהם מקבל, את הפלטים שלו ואת אומדן הקרדיטים להרצה:

```bash
curl -s "https://app.nodaro.ai/v1/api/schema?workflowId=$WORKFLOW_ID" \
  -H "Authorization: Bearer $NODARO_API_KEY"
```

```json
{
"workflowId": "8c2d7f1e-3a4b-4c5d-9e6f-7a8b9c0d1e2f",
"name": "Sunset stills",
"estimatedCredits": 50,
"inputs": [
{ "nodeId": "text-prompt-1", "key": "text", "label": "Prompt", "type": "text" }
],
"outputs": [
{ "nodeId": "generate-image-1", "label": "Generate Image", "type": "image" }
]
}
```

כשבתהליך יש רכיב LLM שמשלמים עליו לפי השימוש בפועל, התגובה מוסיפה `estimatedCreditsIsEstimate: true`, והשדה `estimatedCredits` הוא אומדן: הציגו אותו כ-`≈N`. התגובה מתארת גם תהליך שמציין מודל LLM שעדיין אין לו מחיר. במקרה הזה השדה `estimatedCredits` מושמט, והשדה `estimatedCreditsUnpriced: { reason: "llm_model_unpriced", modelId, message }` מסביר למה. ההרצה של תהליך כזה נדחית. ראו בדף ה-API [מודל שעדיין אין לו מחיר](https://nodaro.ai/docs/developers/api/errors#llm-model-unpriced).

### התחלת ההרצה
```bash
curl -s -X POST https://app.nodaro.ai/v1/api/run \
  -H "Authorization: Bearer $NODARO_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
"workflowId": "8c2d7f1e-3a4b-4c5d-9e6f-7a8b9c0d1e2f",
"inputs": {
"text-prompt-1": { "text": "a cat at sunset" }
}
}'
```

```json
{ "executionId": "3f9e2b1a-7c6d-4e5f-8a9b-0c1d2e3f4a5b", "status": "pending" }
```

### בדיקת הסטטוס עד שההרצה מסתיימת
הנתיב `GET /v1/api/status/:execId` מחזיר את הסטטוס, את ספירת הרכיבים ואת הקרדיטים שהוצאו עד כה. בדקו את הסטטוס כל 2–5 שניות, עד שהוא `completed`, `failed`, `cancelled`, `timed_out` או `discarded`.

### קבלת התוצאה
הנתיב `GET /v1/api/result/:execId` מחזיר את הפלטים של הרצה בסטטוס `completed` או `failed`. בהרצה שהסתיימה בסטטוס `cancelled`, `timed_out` או `discarded`, השתמשו במקום זאת ב-`errorMessage` מתגובת הסטטוס: להרצות האלה אין מטען של תוצאה, ונתיב התוצאה ממשיך להחזיר `202`.

```json
{
"executionId": "3f9e2b1a-7c6d-4e5f-8a9b-0c1d2e3f4a5b",
"status": "completed",
"outcome": "succeeded",
"creditsUsed": 50,
"durationMs": 12450,
"errorMessage": null,
"outputs": [
{
"nodeId": "generate-image-1",
"label": "Generate Image",
"type": "image",
"url": "https://…/output.png"
}
]
}
```

הרצף כולו כסקריפט אחד, ואותן קריאות מ-TypeScript:

**curl**

```bash
BASE="https://app.nodaro.ai"
WORKFLOW_ID="8c2d7f1e-3a4b-4c5d-9e6f-7a8b9c0d1e2f"

EXEC=$(curl -s -X POST "$BASE/v1/api/run" \
  -H "Authorization: Bearer $NODARO_API_KEY" \
  -H "Content-Type: application/json" \
  -d "{\"workflowId\": \"$WORKFLOW_ID\", \"inputs\": {\"text-prompt-1\": {\"text\": \"a cat at sunset\"}}}" \
| jq -r .executionId)

while true; do
STATUS=$(curl -s -H "Authorization: Bearer $NODARO_API_KEY" \
"$BASE/v1/api/status/$EXEC" | jq -r .status)
echo "Status: $STATUS"
case "$STATUS" in completed|failed|cancelled|timed_out|discarded) break;; esac
sleep 5
done

curl -s -H "Authorization: Bearer $NODARO_API_KEY" "$BASE/v1/api/result/$EXEC" | jq .
```

**TypeScript SDK**

```ts
// The /v1/api/ lane has no dedicated SDK method: call it with client.request.
const schema = await client.request('GET', '/v1/api/schema', {
query: { workflowId },
})

const { executionId } = await client.request<{ executionId: string }>('POST', '/v1/api/run', {
body: { workflowId, inputs: { 'text-prompt-1': { text: 'a cat at sunset' } } },
})

const final = ['completed', 'failed', 'cancelled', 'timed_out', 'discarded']
let status = 'pending'
while (!final.includes(status)) {
await new Promise((r) => setTimeout(r, 3_000))
;({ status } = await client.request<{ status: string }>('GET', `/v1/api/status/${executionId}`))
}

const result = await client.request('GET', `/v1/api/result/${executionId}`)
```

ה-CLI מריץ תהליכים רק עם הערכים השמורים שלהם. כדי להעביר ערכים מהטרמינל, פרסמו את התהליך כאפליקציה והריצו את הפקודה `nodaro apps run <slug> --input prompt="…"`.

<TypeTable
type={{
workflowId: { type: 'string (uuid)', description: 'התהליך להרצה.', required: true },
inputs: { type: 'object', description: 'אופציונלי. ערכים חדשים לרכיבי קלט, כשהמפתח הוא מזהה הרכיב או תווית ייחודית של הרכיב. כל ערך הוא אובייקט של שדות לאותו רכיב, למשל ‎{ text: "…" }‎.' },
}}
/>

אסימון מוגבל לתהליכים יכול להריץ רק את התהליכים שבמגבלת התהליכים שלו. בקשה לכל תהליך אחר נדחית עם `403 forbidden`. הנתיבים `POST /v1/api/run` ו-`GET /v1/api/workflows` נספרים במגבלה לדקה של האסימון; קריאות הסטטוס, התוצאה והסכמה לא נספרות. ראו את דף ה-API [מגבלות קצב](https://nodaro.ai/docs/developers/api/rate-limits).

## סינכרוני או אסינכרוני
הנתיב `POST /v1/api/run` אסינכרוני כברירת מחדל: הוא מחזיר מיד `202 Accepted` עם `{ executionId, status: "pending" }`, ומשם בודקים את הסטטוס מדי כמה שניות.

בתהליך קצר, השאירו את החיבור פתוח עד שההרצה מסתיימת:

```http
POST /v1/api/run?wait=true&timeout=120
```

- השרת בודק את ההרצה כל 5 שניות, למשך `timeout` שניות לכל היותר. ברירת המחדל היא 120 והמקסימום הוא 600.
- אם ההרצה מסתיימת בזמן, התגובה היא אותו מטען כמו של `GET /v1/api/result/:execId`. ה-`status` שלו הוא `completed`, `failed`, `cancelled`, `timed_out` או `discarded`.
- אם לא, התגובה היא `202` עם `{ executionId, status: "pending" }`, ומשם בודקים את הסטטוס מדי כמה שניות.

השתמשו בצורה הסינכרונית בהרצות שאתם מצפים שיסתיימו בפחות מדקה, כגון יצירת טקסט ועבודה קלה על תמונות. השתמשו בצורה האסינכרונית בתהליכים שמרנדרים וידאו או מגדילים וידאו. ב-SDK, הגדירו ב-`createClient` ערך `timeoutMs` גבוה מה-`timeout` שלכם, כי כברירת מחדל הלקוח מוותר אחרי 60 שניות.

הנתיב `POST /v1/workflows/:id/run` ונתיבי היצירה הם תמיד אסינכרוניים. בהרצה של רכיב יחיד, המתודה `nodes.runAndWait` של ה-SDK בודקת בשבילכם את הסטטוס של המשימה: ראו את דף ה-API [הרצת רכיב יחיד](https://nodaro.ai/docs/developers/api/nodes).

## הרצות שלא מוצאות שום דבר חדש
להרצה מתוזמנת אין לעיתים קרובות מה לעשות, כי בפיד שהיא קוראת אין פוסטים חדשים. הרצה כזו **מסתיימת בסטטוס `completed`, לא `failed`**:

- Nodaro מדלג על רכיב ששולח טקסט למודל או לקול כשהטקסט שלו ריק, והטקסט הזה הגיע מרכיב שלא הפיק כלום בהרצה הזו. הוא מדלג גם על הרכיבים שאחריו. הרכיבים האלה הם [יצירת טקסט](https://nodaro.ai/docs/nodes/automate/prompt), [יצירת תסריט](https://nodaro.ai/docs/nodes/video/generate-script), [טקסט לדיבור](https://nodaro.ai/docs/nodes/audio/text-to-speech), [יצירת מוזיקה](https://nodaro.ai/docs/nodes/audio/generate-music) ו[טקסט לאודיו](https://nodaro.ai/docs/nodes/audio/text-to-audio).
- Nodaro מדלג גם על רכיב פלט ששולח את מה שמחובר אליו, כגון רכיב [פלט Webhook](https://nodaro.ai/docs/nodes/publish/webhook-output) ורכיבי הפרסום לרשתות החברתיות, כשאף אחד מהחיבורים שלו לא העביר כלום בהרצה הזו.
- על רכיב שדולג לא משלמים. רכיב [פיד ערוץ טלגרם](https://nodaro.ai/docs/nodes/automate/telegram-channel-feed) שלא מחזיר פוסטים חדשים לא עולה כלום.

בהרצה כזו, הנתיבים `GET /v1/api/status/:execId` ו-`GET /v1/api/result/:execId`, והתגובה של `?wait=true`, כוללים `"outcome": "nothing_new"`. בכל הרצה אחרת שהושלמה, הערך הוא `"succeeded"`. בהרצה שנכשלה או שבוטלה אין `outcome`, רק `status`. השדה `errorMessage` נשאר `null`, כי שום דבר לא השתבש. ב-`GET /v1/workflow-executions/:id`, המצב של כל רכיב שדולג הוא `{ "status": "skipped", "skipReason": "empty_input" }`.

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

## מה הרצה לא יכולה לשנות
בקשת הרצה לא יכולה לשנות את היעדים של רכיבים שפונים לשירות חיצוני. התהליך עצמו קובע לאן הוא שולח נתונים ומאיפה הוא מביא אותם, בכל נקודת קצה של הרצה, כולל `POST /v1/workflows/:id/run`, `POST /v1/api/run` ו-`POST /v1/app/:slug/run`.

הרכיבים שפונים לשירות חיצוני הם רכיב [פלט Webhook](https://nodaro.ai/docs/nodes/publish/webhook-output), רכיבי הפרסום לרשתות החברתיות, כגון רכיב [פרסום לרשתות](https://nodaro.ai/docs/nodes/publish/publish-to-social), והרכיבים שמביאים נתונים מבחוץ, כגון [גריפת אתר](https://nodaro.ai/docs/nodes/automate/web-scrape), [פיד ערוץ טלגרם](https://nodaro.ai/docs/nodes/automate/telegram-channel-feed) ו[כתובת וידאו](https://nodaro.ai/docs/nodes/automate/video-url). ברכיבים האלה, דריסה לא יכולה לשנות:

- שום שדה שהשם שלו מסתיים ב-`Url` או ב-`Urls`;
- את `target`, `targets`, `query`, `channel`, `chatId`, `connectionId`, `credentialId`, `platform`, `webhook`, `endpoint`, `host` או `privacy`;
- את שדות הבחירה `actor` ו-`mode`, שקובעים מאיזה שדה יעד הרכיב קורא.

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

עדיין אפשר לדרוס שדות רגילים ברכיבים האלה, כגון כיתוב או מגבלה, ואת שדות ה-`url` של המדיה ברכיבי קלט, כגון העלאות ואודיו ייחוס.

הרצה גם לא יכולה לשנות מה רכיב [תת־תהליך](https://nodaro.ai/docs/nodes/automate/sub-workflow) מריץ. ניסיון לשנות את `workflowId`, `selectedRouteId` או `routeSnapshot` שלו נדחה עם `400 locked_field`, בין אם קובעים אותם ישירות ובין אם דרך רשומה של `fieldMappings`. התהליך עצמו קובע איזה תהליך הרכיב מריץ, ואיזה חלק ממנו.

ברכיבים של סרטון UGC, הרצה יכולה לקבוע רק את `source`, `gender` ו-`photoUrl` של רכיב יוצר/ת UGC, ואת `targetDurationSec` של רכיב תסריט UGC. בקשה שקובעת כל שדה אחר, כגון תוצאה שנשמרה, תסריט או קליפ, נדחית עם `400 locked_field`. הרצה של רכיב יוצר/ת UGC שמוגדר לאפשרות **התמונה שלי** נדחית עם `400 ugc_photo_required` לפני שנגבים קרדיטים, אם מי שמריץ אותה לא נתן תמונה של עצמו. ראו את הדף [יוצר/ת UGC](https://nodaro.ai/docs/nodes/video/ugc-creator) ואת הסעיף [סרטון UGC באפליקציה](https://nodaro.ai/docs/concepts/apps#a-ugc-video-in-an-app).

באפליקציה שפורסמה, כשהאפליקציה חושפת מודל או הגדרה מהירה עם רשימה קבועה של אפשרויות, הערך מוגבל לרשימה הזו: ערך מחוץ לרשימה נדחה עם `400 validation_error`, וכך גם מספר מחוץ לטווח של הגדרה. ערך `null` ב-`inputOverrides` מנקה את השדה הזה ברכיב בהרצה הזו, ולכן ברירת המחדל של הרכיב עצמו חלה. ערך `null` בקלט שטוח, לעומת זאת, מושמט, ולכן הערך השמור חל. ראו את הסעיף [שדות של מודל ושל הגדרות מהירות](https://nodaro.ai/docs/developers/embed/miniapps#quick-settings) בדף הטמעת מיני־אפליקציה.

## תיקוני פרמטרים
נתיבי היצירה של תמונות ושל סרטונים מקבלים אוסף אחד של ערכים ל-`aspectRatio`, ל-`resolution` ול-`quality`, בלי קשר למודל. אף מודל לא תומך בכל האוסף. במקום לדחות ערך שהמודל שנבחר לא מקבל, השרת **מתקן** אותו לערך שהמודל כן מקבל, ומודיע לכם מה הוא שינה. דחייה באמצע תהליך הייתה מכשילה גם את הרכיבים שרצו לצד הרכיב שנדחה, שכבר יצרו תוצאה ושכבר שילמתם עליהם.

הנתיבים שמתקנים ערכים הם `POST /v1/generate-image`, `/v1/image-to-image`, `/v1/edit-image`, `/v1/text-to-video` ו-`/v1/generate-video`:

```json
{
"jobId": "0f1a9c2e-5b7d-4e8a-9c31-6d2f8b4a7e10",
"adjustments": [
{
"field": "aspectRatio",
"from": "3:2",
"to": "auto",
"reason": "GPT Image 2 does not support aspectRatio \"3:2\" — using \"auto\" instead. Supported: auto, 1:1, 16:9, 9:16, 4:3, 3:4."
},
{
"field": "resolution",
"from": "4K",
"to": "1K",
"reason": "GPT Image 2 only renders 1K at the \"auto\" aspect ratio."
}
]
}
```

- **`adjustments` לא מופיע כששום דבר לא השתנה.** התגובה לבקשה תקינה היא בדיוק `{ "jobId": "…" }`.
- **`to` לא מופיע כשלמודל אין הגדרה כזו** והערך הושמט, למשל `aspectRatio` שנשלח למודל הגדלה.
- **הקרדיטים נקבעים לפי הערך המתוקן.** בקשה ל-[GPT Image 2](https://nodaro.ai/docs/models/image/gpt-image-2) עם `auto` ב-`2K` מרונדרת ומחויבת ב-1K, כי `auto` מרנדר רק ב-1K.
- **`aspectRatio: "auto"` שומר על הצורה של התמונה שלכם** ב-`/v1/image-to-image` וב-`/v1/edit-image`. מודל שיש לו `auto` משלו מקבל אותו כמו שהוא. במודל בלי `auto` כזה, השרת קורא את הגודל של `imageUrl`, משתמש ביחס הקרוב ביותר שהמודל תומך בו ומדווח על הבחירה ב-`adjustments`. למשל, תמונה לאורך בגודל 1104 × 1472 שנשלחת ל-`seedream-5-pro-i2i` מרונדרת ב-`3:4`. כשאי אפשר לקרוא את הגודל, הערך `auto` חוזר ליחס הראשון ברשימה של המודל. במודלים האלה היחס אף פעם לא משנה את המחיר.
- **`/v1/generate-image` עושה את אותו הדבר כשתמונה מחוברת.** התמונה היא התמונה הראשונה שהבקשה שולחת למודל: השדה `baseImageUrl` (השלמה או עידון) כשהוא מוגדר, ואחרת תמונת הייחוס הראשונה (`referenceImageUrls`, או הראשונה שנבנית מ-`connectedReferences`). בלי תמונה, הערך `auto` במודל בלי `auto` משלו נותן את היחס הראשון ברשימה של המודל.
- **גם תהליכים שמורים מתוקנים.** תהליך שנשמר דרך ה-API או דרך MCP מקבל את אותם תיקונים כשהוא נכתב. החריג הוא `auto` ברכיב מסוג `generate-image`, `image-to-image`, `modify-image` או `edit-image`: הוא נשמר כמו שהוא, ומקבל ערך לפי התמונה כשהתהליך רץ.
- **הערכים שכל מודל מקבל** מופיעים ב-`GET /v1/models`. ראו בדף ה-API [גילוי מודלים](https://nodaro.ai/docs/developers/api/nodes#discover-models).

### תיקונים בנתיבי הווידאו
הנתיבים `/v1/text-to-video` ו-`/v1/generate-video` מחזירים `adjustments` באותו מבנה. הנתיב `/v1/generate-video` גם חוזר על ה-`reason` של כל תיקון במערך `warnings` שלו, לצד אזהרות שלא קשורות לפרמטרים, כגון `voice_unsupported_for_provider`. בדקו את `adjustments` כשאתם צריכים לדעת איזו הגדרה זזה. השניים אף פעם לא סותרים זה את זה.

- **ערך שאין לו תמיכה עובר לאפשרות הקרובה ביותר**, אף פעם לא לזולה ביותר או לראשונה. בקשה ל-`4k` במודל שמגיע לכל היותר ל-1080p מרונדרת ב-1080p, לא ב-480p, ויחס `9:21` לאורך הופך ל-`9:16`, לא ל-`16:9`.
- **כש-`resolution` מושמט, הבקשה נשלחת ברמת הרזולוציה שלפיה היא מתומחרת.** כשהפלטפורמה מגדירה למודל רמת ברירת מחדל, משלמים לפי הרמה הזו, והמודל מתבקש לרנדר בה. הערך מופיע ב-`input_data` של המשימה, כך שתמיד אפשר לראות מה נשלח.
- **צורת הכתיבה מתוקננת לפני התמחור.** הערך `4K` מתפרש כ-`4k`, ולכן הוא מתומחר ומרונדר ברמת ה-4K.
- **חלק מהמודלים מרנדרים רמה קבועה לכל ערך אחר.** [MiniMax H3](https://nodaro.ai/docs/models/video/minimax-h3) מרנדר 2K לכל ערך שהוא לא `768P`, ומשפחת [Wan 3.0](https://nodaro.ai/docs/models/video/wan-3-0) מרנדרת 720p. במודלים האלה, התיקון מכוון לרמה שהמודל ייצר בפועל, כך שהמחיר תואם לרינדור. הרשומה של המודל ב-`GET /v1/models` מצהירה על כך ב-`unlistedResolutionRendersAs`.
- **משלמים לפי האורך שהמודל מרנדר.** בקשה בלי `duration` מרונדרת, ומחויבת, לפי אורך ברירת המחדל של המודל עצמו, `defaultDuration` ב-`GET /v1/models`. ערך `duration` שהמודל לא מציע עובר לאורך הקרוב ביותר שהוא כן מציע, ובתיקו נבחר האורך הקצר יותר. השינוי מדווח ב-`adjustments`. למשל, בקשה ל-7 שניות ב-Kling מרונדרת ומחויבת לפי 5 שניות, ובמודל [LTX 2.3](https://nodaro.ai/docs/models/video/ltx-2-3-pro) היא מרונדרת ומחויבת לפי 6 שניות.
- **`duration: -1` פירושו אוטומטי** במודלים שתומכים בכך, משפחת [Seedance 2](https://nodaro.ai/docs/models/video/seedance-2) (`autoDuration: true` ב-`GET /v1/models`). המודל בוחר את אורך הקליפ, שהוא האורך של קליפ המקור כשהמודל עורך סרטון ייחוס. בהרצה במצב אוטומטי משוריינים קרדיטים לפי הקליפ הארוך ביותר של המודל, והקרדיטים על ההפרש מוחזרים לפי האורך שנמסר. עם סרטוני ייחוס שהאורך שלהם נמדד, השריון נקבע במקום זאת לפי האורך של הקליפ הראשון, מעוגל כלפי מעלה לשנייה שלמה ועוד שנייה אחת, ולעולם לא יותר מהקליפ הארוך ביותר של המודל. מודלים אחרים מתעלמים מ-`-1` ומרנדרים את אורך ברירת המחדל שלהם.

נתיבי התמונות של דמויות ומיקומים (`/v1/generate-character`, `/v1/generate-character-asset`, `/v1/generate-location` ו-`/v1/generate-location-asset`) מתקנים את `quality` ואת `resolution` באותו אופן, אבל לא מחזירים `adjustments`. הערך המתוקן מופיע רק ב-`input_data` של המשימה, מ-`GET /v1/jobs/:id`.

## ניהול תהליכים
### רשימה וקריאה
הנתיב `GET /v1/projects/:projectId/workflows` מחזיר את התהליכים של פרויקט בלי `nodes`, `edges` ו-`settings`. הנתיב `GET /v1/workflows/:id` מחזיר תהליך אחד במלואו. בארגון, הרשימה נקבעת לפי סביבת העבודה שבה אתם פועלים: ראו את דף ה-API [סביבות עבודה](https://nodaro.ai/docs/developers/api/workspaces).

**curl**

```bash
curl -s https://app.nodaro.ai/v1/projects/$PROJECT_ID/workflows \
  -H "Authorization: Bearer $NODARO_API_KEY" | jq '.data[] | {id, name}'
```

**TypeScript SDK**

```ts
const { data: workflows } = await client.workflows.list({ projectId })
const { data: workflow } = await client.workflows.get(workflows[0].id)
console.log(workflow.nodes.length)
```

**CLI**

```bash
nodaro workflows list --project $PROJECT_ID --json
nodaro workflows get 8c2d7f1e-3a4b-4c5d-9e6f-7a8b9c0d1e2f
```

### יצירה ועדכון
צרו תהליך בפרויקט עם `POST /v1/projects/:projectId/workflows`. כל השדות חוץ מהפרויקט הם אופציונליים, ושדה שלא שולחים מקבל את ברירת המחדל של השרת:

```ts
const { data: wf } = await client.workflows.create({
projectId,
name: 'My workflow',
nodes: [],
edges: [],
})
```

הנתיב `PATCH /v1/workflows/:id` משנה כל צירוף של שדות. כדי שעדכון יהיה בטוח מפני עריכה במקביל, שלחו `expectedVersion`, הערך השלם של `version` מהקריאה האחרונה שלכם. כשהתהליך השתנה מאז, העדכון נדחה עם `409 workflow_conflict`, והשגיאה כוללת את `currentVersion`, `currentUpdatedAt` ו-`currentRecord`, התהליך הנוכחי כולו. מזגו את השינוי שלכם לתוך `currentRecord` ושמרו שוב, בלי קריאה נוספת. השדה `expectedUpdatedAt`, חותמת זמן, עובד באותו אופן.

```ts

try {
await client.workflows.update(id, { name: 'Renamed', expectedVersion: 7 })
} catch (err) {
if (err instanceof WorkflowConflictError && err.currentRecord) {
await client.workflows.update(id, { name: 'Renamed', expectedVersion: err.currentVersion })
} else throw err
}
```

- ערכי מצב ההרצה ב-`data` של רכיב, כגון סטטוס ההרצה שלו, המשימה הנוכחית וההתקדמות, מוסרים בשמירה ואף פעם לא נשמרים.
- השדה `thumbnailUrl` קובע את תמונת השער של התהליך מכתובת של תמונה שכבר מאוחסנת. הערך `null` מנקה אותה.
- שמירה ששולחת רק `edges` ומחברת שכבה של רכיב [שכבה על וידאו](https://nodaro.ai/docs/nodes/video/video-overlay) כותבת מחדש את הרכיב הזה, ולכן היא נדחית עם `409 workflow_conflict` כשהתהליך השתנה מאז שקראתם אותו, גם בלי `expectedVersion`.
- השדה `visibility` הוא `private` או `workspace`. רק היוצר או מנהל של סביבת העבודה יכולים לשנות אותו, ובקשה לתהליך שלא נמצא בסביבת עבודה נדחית עם `400 not_workspace_scoped`.

### מחיקה
הנתיב `DELETE /v1/workflows/:id` מוחק תהליך. היוצר ומנהל של סביבת העבודה יכולים למחוק אותו, אבל שותף אף פעם לא. מחיקה של תהליך שלא קיים, או שאתם לא יכולים לראות, נדחית עם `404`, כך שמחיקה שלא קרתה אף פעם לא נראית כמו הצלחה. בקשה של שותף שיכול לראות את התהליך אבל אין לו הרשאה למחוק אותו נדחית עם `403`.

### ייצוא וייבוא
הנתיב `GET /v1/workflows/:id/export` מחזיר את התהליך כחבילת JSON ניידת. עם `?assets=true`, החבילה כוללת גם את הדמויות, האובייקטים והמיקומים שהתהליך משתמש בהם, אם הם שלכם. כשרכיבים מצביעים על מדיה שהתקנה אחרת לא יכולה להגיע אליה, כגון קבצים ב-`localhost` או ברשת פרטית, החבילה מפרטת אותה ב-`portability.unreachableMedia`. מי שיש לו גישת צפייה מקבל את התהליך בלי טיוטות ה-Studio של הבעלים: משבצות המדיה הריקות, הסימונים של הרצות שעדיין רצות ותוכניות הקול של הטייקים. במקרה כזה, החבילה לא כוללת נכס שרק החלקים האלה משתמשים בו. גם `GET /v1/workflows/:id` משמיט את אותם חלקים בשביל מי שיש לו גישת צפייה.

הנתיב `POST /v1/workflows/import` יוצר תהליך מחבילה:

```http
POST /v1/workflows/import
{ "projectId": "<project uuid>", "workflow_json": { "version": 1, "name": "…", "nodes": [], "edges": [] } }
```

הייבוא יוצר מחדש בחשבון שלכם את הדמויות, האובייקטים, היצורים והמיקומים שבחבילה, ומעדכן את הרכיבים כך שיצביעו עליהם. הוא מעתיק מדיה נגישה לאחסון של ההתקנה הזו: עד 25 קבצים למדיה של התהליך ועוד 25 לנכסים שבחבילה, עם תמונות עד 20 MB ווידאו או אודיו עד 50 MB. התגובה כוללת את התהליך החדש ו-`importReport`:

| שדה | משמעות |
| --- | --- |
| `rehosted` | כמה קבצים הועתקו להתקנה הזו. |
| `unreachable` | מדיה בשרתים פרטיים. הרכיבים ממשיכים להצביע על המקום המקורי שלה, ולא רצים עד שמעלים את הקובץ שוב. |
| `skipped` | מדיה שאי אפשר היה להעתיק, עם הסיבה, כגון `HTTP 404`. |
| `assetIdMap` | כל מזהה של נכס בחבילה, ממופה לרשומה שנוצרה בשבילו. |
| `assetsSkipped` | נכסים שלא היה להם מקום במכסת האחסון שלכם. התהליך בכל זאת נוצר. |

מה-CLI: `nodaro workflows export <id> --with-assets --output bundle.json`, ואז `nodaro workflows import bundle.json --project <projectId>`. ראו את המדריך [ייבוא וייצוא](https://nodaro.ai/docs/guides/import-export) כדי לדעת מה עובר עם התהליך.

### שיחות MCP
```http
GET /v1/mcp-conversations?month=2026-10&sources=claude,chatgpt
```

הנתיב הזה מפרט את שיחות ה-MCP שלכם בחודש אחד, מהחדש לישן. כל שיחה היא תהליך שעוזר AI בנה מהשלבים של השיחה. ראו את הדף [איך שיחות MCP הופכות לתהליכים](https://nodaro.ai/docs/mcp/conversation-flows). אסימון OAuth צריך את `workflows:read`.

| פרמטר שאילתה | משמעות |
| --- | --- |
| `month` | `YYYY-MM`. ברירת המחדל היא החודש האחרון שיש בו שיחות. |
| `sources` | המקורות שנשארים ברשימה, מופרדים בפסיקים: `claude`, `claude-code`, `chatgpt` או שם של לקוח אחר באותיות קטנות. ברירת המחדל היא כל המקורות. |

התגובה כוללת:

- `month`: החודש שמוצג.
- `months`: כל חודש שיש בו שיחות, מהחדש לישן, עם מספר השיחות בו.
- `sources`: המקורות של החודש שמוצג, עם מספר השיחות של כל אחד. מקור מאחד את החיבורים של מוצר אחד: "Claude Code (nodaro)" ו-"Claude Code (staging)" הם מקור `claude-code` אחד. השדה `connections` מפרט אותם.
- `data`: השיחות. לכל שיחה יש:
  - השדות `id` ו-`projectId`. שיחה היא תהליך, ולכן פותחים אותה, משנים את שמה, מייצאים, מעתיקים או מוחקים אותה עם נתיבי התהליכים.
  - השדות `name`, `client`, `clientLabel` ו-`clientName`.
  - השדות `startedAt`, `lastActivityAt` ו-`live`. הערך `live` הוא `true` כל עוד עברו פחות מ-30 דקות מהשלב האחרון.
  - `firstRequest`: הבקשה הראשונה שלכם, כשהעוזר העביר אותה.
  - השדות `nodeCount` ו-`counts` לפי סוג: `image`, `video` ו-`audio`.
  - `credits`: הקרדיטים שנגבו על המשימות של השיחה, או `null` כשלא גובים קרדיטים.
  - `cover`: `{ url, kind, setByUser }`. תמונת השער שקבעתם, ואם לא קבעתם, התוצאה האחרונה.

הנתיב `DELETE /v1/workflows/:id` על שיחה מוחק את התהליך שלה. המשימות שהעוזר יצר לא מתויקות בתהליך, ולכן התמונות והסרטונים שלהן נשארים בספרייה שלכם. הרצות שהתחילו בעורך נמחקות יחד עם התהליך, כמו בכל תהליך. אי אפשר למחוק את התהליך המרכזי של חודש: הנתיב מחזיר `409 mcp_hub`. מפתח API אישי שמוגבל לחלק מהתהליכים לא יכול להציג את רשימת השיחות, ובקשה שלו נדחית עם `403`.

### העברה לפרויקט אחר
```http
POST /v1/workflows/:id/move
{ "projectId": "…" }
```

העברה היא כתיבה לתהליך, ולכן אסימון OAuth צריך את `workflows:write`. גם הנתיב `PATCH /v1/workflows/:id` עם `projectId` עושה את אותו הדבר ופועל לפי אותם כללים. אפשר להעביר עבודה שיצרתם. בתוך ארגון, מנהל של סביבת עבודה יכול גם להעביר עבודה בין שתי סביבות עבודה שהוא מנהל. בפרויקט אישי, גם פרויקט המקור וגם פרויקט היעד חייבים להיות שלכם.

| סטטוס | קוד | משמעות |
| --- | --- | --- |
| 400 | `validation_error` | התהליך כבר נמצא בפרויקט הזה. |
| 403 | `not_permitted` | אין לכם הרשאה להעביר את התהליך, או להעביר אותו לשם. |
| 404 | `not_found` | אין תהליך כזה, או שאין לכם פרויקט כזה. |
| 409 | `move_blocked` | העבודה נוצרה במסגרת מטלה. |
| 409 | `workspace_archived` | סביבת העבודה שאליה מעבירים נמצאת בארכיון. |

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

```json
{
"data": { "id": "8c2d7f1e-3a4b-4c5d-9e6f-7a8b9c0d1e2f", "projectId": "5e4d3c2b-1a09-4f8e-8d7c-6b5a49382716" },
"droppedCollaborators": [{ "userId": "2b3c4d5e-6f70-4a81-92b3-c4d5e6f70812", "name": "Sam" }]
}
```

בצורת ה-`PATCH`, השדה `droppedCollaborators` מופיע רק כשהוסרה הרשאה.

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

## הרשאות OAuth לתהליכים
| הרשאה | נתיבים |
| --- | --- |
| `workflows:read` | `GET /v1/projects/:projectId/workflows`, `GET /v1/workflows`, `GET /v1/workflows/:id`, `GET /v1/workflows/:id/export`, `GET /v1/mcp-conversations` |
| `workflows:write` | יצירה, עדכון, מחיקה, ייבוא והעברה, וגם `POST /v1/workflows/:parentId/sub-workflows` |
| `workflows:execute` | `POST /v1/workflows/:id/run` |

אסימוני API אישיים לא צריכים הרשאות. ראו את הדף [אפליקציות OAuth](https://nodaro.ai/docs/developers/oauth).

## רכיבים של הפקות Studio
חלק מהרכיבים על הקנבס שייכים להפקת Studio ותלויים בפריימים מקושרים. צריך ליצור אותם דרך ה-API של הפקות Studio: הרצה שלהם דרך `POST /v1/workflows/:id/run`, או יצירה ישירה של אחד מהם, נדחית עם `400 sequence_execution_required`. ראו את דף ה-API [הפקות Studio](https://nodaro.ai/docs/developers/api/studio-productions).

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

### איך מריצים תהליך של Nodaro דרך ה-API?

שלחו POST /v1/workflows/:id/run. הבקשה מריצה את התהליך השמור ומחזירה 202 עם executionId, ואיתו בודקים את הסטטוס מדי כמה שניות. כדי לשנות ערכי קלט בהרצה אחת, הוסיפו אובייקט inputOverrides שהמפתחות שלו הם מזהי רכיבים, או השתמשו ב-POST /v1/api/run עם אובייקט inputs.

### איך מעבירים ערכי קלט להרצה של תהליך?

שלחו אובייקט inputs ל-POST /v1/api/run. המפתחות שלו הם מזהי רכיבים או תוויות ייחודיות של רכיבים, ובתוך כל מפתח נמצא השדה של הרכיב, למשל text. נקודת הקצה GET /v1/api/schema מפרטת את שדות הקלט של תהליך.

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

כן. הוסיפו ל-POST /v1/api/run את פרמטרי השאילתה wait=true ו-timeout=120. השרת משאיר את החיבור פתוח עד 600 שניות ומחזיר את הפלטים, או מחזיר 202 עם executionId כשההרצה עדיין נמשכת, ומשם ממשיכים לבדוק את הסטטוס מדי כמה שניות.

### למה ה-API שינה את יחס הגובה־רוחב או את הרזולוציה ששלחתי?

המודל שבחרתם לא תומך בערך הזה, ולכן Nodaro תיקן אותו לערך הקרוב ביותר שהמודל מקבל, במקום להכשיל את ההרצה. התגובה מפרטת כל שינוי בשדה adjustments, והקרדיטים נגבים לפי הערך המתוקן.

### האם הרצה דרך ה-API יכולה לשנות לאן תהליך שולח את התוצאות שלו?

לא. הרצה לא יכולה לדרוס שדות יעד ברכיבים שפונים לשירות חיצוני, כגון הכתובת של רכיב פלט Webhook או החשבון של רכיב שמפרסם ברשתות החברתיות. בקשה שמנסה לעשות את זה נדחית עם 400 locked_field.
