# הפקות Studio

> קריאה ועריכה של הפקות Studio ב-REST API: פעולות אטומיות, יצירת תמונות סטילס וקליפים, הכנסת תוצאות להפקה, תכנון הייצוא, שיתוף והעתקה.

Source: https://nodaro.ai/he/docs/developers/api/studio-productions

**ה-API של הפקות Studio** קורא וכותב את הסרטים שאתם בונים ב-Nodaro Studio. הפקה היא תהליך של Nodaro שבהגדרות שלו נמצאת רשימה של שוטים לפי הסדר. לכל שוט יש תמונת סטילס של הפריים, קליפ מונפש אופציונלי, והתוכנית, המראות, הליהוק והקול שיצרו אותם. סקריפט, עוזר AI ועורך ה-Studio עובדים כולם על אותה הפקה דרך הנתיבים האלה, באותו זמן.

הפקות Studio זמינות רק ב-Nodaro Cloud. פריסה שלא מספקת אותן מחזירה `404` בכל נתיב, ולכן בדקו פעם אחת עם נתיב הרשימה אם היכולת קיימת. בהתקנה באירוח עצמי, בנו את הסרט כתהליך עם רכיבים כמו [סצנה](https://nodaro.ai/docs/nodes/video/scene), [יצירת וידאו](https://nodaro.ai/docs/nodes/video/generate-video) ו[שילוב סרטונים](https://nodaro.ai/docs/nodes/video/combine-videos). הנתיבים מקבלים אסימון Bearer; אסימונים של אפליקציית OAuth צריכים `workflows:read` לקריאה ו-`workflows:write` לכתיבה. ראו את דף ה-API [אימות](https://nodaro.ai/docs/developers/api/authentication).

כשאין לכם גישה להפקה, כל נתיב מחזיר `404`, ולעולם לא `403`, כך שאי אפשר לברר אם מזהה מסוים קיים.

### המילים של העורך והמפתחות של המסמך
העורך והמסמך קוראים לאותם דברים בשמות שונים. בקוד השתמשו במפתחות של המסמך, ומול אנשים השתמשו במילים של העורך.

| בעורך | במסמך |
| --- | --- |
| סרט | ההפקה |
| סצנה, למשל ״סצנה 3״ | רשומה ב-`shots[]`, שמזוהה לפי ה-`shotId` שלה |
| הפריים של סצנה, והטייקים שלו | תוצאות ה-`still` של השוט |
| התנועה של סצנה, והטייקים שלה | תוצאות ה-`clip` של השוט |
| השוטים שבתוך תנועה | ה-`beats[]` של השוט |

## נקודות קצה
| שיטה | נתיב | מה היא עושה |
| --- | --- | --- |
| `GET` | `/v1/studio/productions/skill` | המדריך לכתיבה, הקטלוג, ה-JSON Schema של התוכנית ורשימת הפעולות. הנתיב חינמי. |
| `GET` | `/v1/studio/productions/capabilities` | אילו פעולות אופציונליות הפריסה הזו תומכת בהן. |
| `POST` | `/v1/studio/productions/validate` | בדיקת תוכנית. הנתיב חינמי ולא שומר כלום. |
| `GET` | `/v1/studio/productions` | רשימת ההפקות שלכם, מהחדש לישן. |
| `POST` | `/v1/studio/productions` | יצירת הפקה, מתוכנית או בלעדיה. |
| `GET` | `/v1/studio/productions/:id` | קריאת הפקה. הנתיב אף פעם לא כותב. |
| `POST` | `/v1/studio/productions/:id/ops` | החלת אצווה של פעולות, או תצוגה מקדימה שלה. |
| `POST` | `/v1/studio/productions/:id/reconcile` | הפיכת משימות שהסתיימו לתוצאות. |
| `POST` | `/v1/studio/productions/:id/import` | הוספת השוטים של תוכנית להפקה. |
| `POST` | `/v1/studio/productions/:id/describe` | כתיבת התוכנית מתוך בריף. |
| `POST` | `/v1/studio/productions/:id/generate` | יצירת תמונת סטילס או קליפ לשוט, או אומדן מחיר ליצירה. |
| `POST` | `/v1/studio/productions/:id/frame` | שליפת פריים מקליפ. |
| `POST` | `/v1/studio/productions/:id/voice` | הקראת שורה על גבי שוט. |
| `POST` | `/v1/studio/productions/:id/revoice` | החלפת הקולות בקליפ של שוט. |
| `POST` | `/v1/studio/productions/:id/music` | יצירת הפסקול. |
| `GET` | `/v1/studio/productions/:id/export-plan` | השלבים שמרכיבים את הסרט, עם המחיר של כל אחד. |
| `POST` | `/v1/studio/productions/:id/share` | הפעלה או כיבוי של שיתוף בקישור. |
| `POST` | `/v1/studio/productions/:id/unshare` | כיבוי של שיתוף בקישור. |
| `POST` | `/v1/studio/productions/:id/clone` | העתקת הפקה. |

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

## קריאת הפקה
כל תגובה עטופה כ-`{ "data": { "production": { … } } }`. ה-SDK פותח את המעטפת: מתודה שמחזירה הפקה מחזירה ישירות את ההפקה עצמה. ברמה העליונה של ההפקה נמצאים:

| שדה | מה יש בו |
| --- | --- |
| `id`, `name`, `version`, `updatedAt` | הזהות של ההפקה, והמונה שכל כתיבה בודקת. |
| `thumbnailUrl`, `shared`, `archived` | תמונת הפוסטר, האם השיתוף בקישור מופעל, והאם ההפקה בארכיון. |
| `film`, `cast`, `folders`, `storyboard`, `music`, `musicPlan`, `cuts` | מראה הסרט, התפקידים, התיקיות בציר הזמן, הבריף, הפסקול וגרסאות העריכה שיוצאו. |
| `trash` | `{ count, items? }`: מה שהוסר ואפשר לשחזר. |
| `pending` | `{ stills, clips, music, draft }`: מה שנמצא כרגע ביצירה. |
| `shots[]` | השוטים לפי הסדר בציר הזמן, כל אחד עם `still`, `clip`, `startFrame`, `endFrame`, `plan`, `beats`, `look`, `voice` ועוד. |

הנתיב `GET /v1/studio/productions/:id` מקבל `detail=summary` (ברירת המחדל: ספירות והכתובות הפעילות) או `detail=full` (כל תוצאה בהיסטוריה של כל שוט, עם ההקשר שיצר אותה). הפרמטר `shot_id` מחזיר שוט אחד, וזו הדרך הזולה לקרוא שוב אחרי יצירה. הנתיב `GET /v1/studio/productions` מקבל `limit`, `cursor` ו-`includeArchived`.

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

**curl**

```bash
curl "https://app.nodaro.ai/v1/studio/productions/5d8f2b4a-7c1e-4a9d-8b3f-2e6c9a1d4f7b?detail=summary" \
  -H "Authorization: Bearer $NODARO_API_KEY"
```

**TypeScript SDK**

```ts

const client = createClient({
baseUrl: 'https://app.nodaro.ai',
auth: new StaticTokenAuth(process.env.NODARO_API_KEY!),
})

const production = await client.studio.productions.get(id, { detail: 'full' })
const shot = await client.studio.productions.get(id, { shotId: 'shot-2' })
```

ה-SDK מגדיר טיפוסים למעטפות (`version`, `rebased`, `receipts`, `warnings`, ה-`credits` של אומדן מחיר, ה-`jobIds` של הרצה), ומשאיר את מסמך ההפקה כ-JSON פתוח.

## עריכה בפעולות
כל שינוי בהפקה הוא פעולה: עריכה עם שם, כמו שינוי השם של שוט, הזזה שלו, שיוך של תפקיד בליהוק או שמירה של מועמד. הנתיב `POST /v1/studio/productions/:id/ops` בודק ומחיל אצווה של פעולות. רשימת הפעולות מגיעה מהשרת ולא מודפסת כאן: קבלו את החלק `operating` מ-`GET …/skill`, שתמיד תואם לפריסה שאתם עובדים מולה.

**curl**

```bash
# batch.json: { "ops": [ ...operations from GET /v1/studio/productions/skill... ], "baseVersion": 7 }
curl -X POST https://app.nodaro.ai/v1/studio/productions/5d8f2b4a-7c1e-4a9d-8b3f-2e6c9a1d4f7b/ops \
  -H "Authorization: Bearer $NODARO_API_KEY" \
  -H "Content-Type: application/json" \
  -d @batch.json
```

**TypeScript SDK**

```ts
const { production, version, rebased, receipts } =
await client.studio.productions.ops(id, { ops, baseVersion: 7 })
```

התגובה היא `{ production, version, rebased, receipts, warnings }`. שישה כללים חלים על כל אצווה:

1. **הכול או כלום.** פעולה לא תקינה אחת גורמת לדחיית האצווה כולה, ושום דבר לא נכתב. השגיאה מציינת את הפעולה לפי `opIndex` שלה, שנספר מ-0. אצווה יכולה לכלול עד 100 פעולות.
2. **החלה מחדש על הגרסה העדכנית, כברירת מחדל.** השדה `baseVersion` הוא למידע בלבד: אצווה שנכתבה מול גרסה ישנה יותר מוחלת על המסמך החדש ביותר, והתגובה מציינת `rebased: true`. שלחו `strict: true` כדי לקבל במקום זאת `409 workflow_conflict`.
3. **זהו כל דבר לפי מפתח יציב.** שוט לפי המזהה שלו, תיקייה או גרסת עריכה לפי המזהה שלהן, אלמנט בליהוק לפי ה-slug של התפקיד שלו, תוצאה לפי מפתח התוצאה שלה. אף פעם לא לפי מיקום, כי ייתכן שהעורך וסקריפט עורכים את אותה הפקה באותו זמן.
4. **צרו את המזהים בעצמכם.** פעולה שיוצרת שוט, תיקייה, גרסת עריכה או עותק מקבלת את המזהה שאתם נותנים לה. כך העותק המקומי שלכם והשרת מסכימים על כל מזהה.
5. **מחיקות עוברות לפח.** הסרה מעבירה את מה שהוסר לפח של ההפקה, ופעולה משחזרת אותו. רק מחיקה סופית מפורשת משמידה משהו.
6. **אמצו את התגובה.** החליפו את העותק שלכם ב-`production` במקום למזג, ושלחו את `version` בתור ה-`baseVersion` הבא.

שיתוף הוא לא פעולה; אצווה שמנסה לשנות אותו נדחית. באישורי הביצוע (`receipts`) יש שורה אחת בזמן עבר לכל פעולה, `{ op, summary, ids?, impact? }`: הציגו אותם לכל מי שרוצה לדעת מה עוזר עשה. השדה `warnings` מפרט דברים שכדאי לציין אבל הם לא כשלים, כמו פעולה שלא שינתה כלום.

אישור ביצוע יכול לכלול גם `detail`, `{ template, vars }`: אותה שורה כנתונים. התשובה יכולה לכלול `warningDetails`, אותו דבר עבור `warnings`. כך לקוח יכול לנסח את התשובה בשפה של האדם שמולו. השדות `summary` ו-`warnings` נשארים בניסוח האנגלי, והם תמיד מופיעים. לכן לקוח שאין לו ניסוח ל-`template`, או שה-`vars` שלו לא ממלאים אותו, מציג את השורה באנגלית ולא מפסיד כלום.

- מזהה של תבנית ניסוח הוא קבוע. שינוי במקומות למילוי שלה או במשמעות שלה מתפרסם עם מזהה חדש.
- השדה `warningDetails` נשלח רק כשהאורך שלו זהה בדיוק לאורך של `warnings`. רשומה `i` מנסחת את אזהרה `i`, והערך `null` מסמן אזהרה שיש לה רק ניסוח באנגלית. התייחסו לרשימה בכל אורך אחר כאילו היא לא קיימת.
- פריסה ישנה יותר מהשדות האלה לא שולחת אף אחד מהם.

### תצוגה מקדימה של אצווה לפני ההחלה
הערך `dryRun: true` שואל מה האצווה הייתה עושה. השרת מריץ את הכתיבה עצמה, באותו הקשר ועם אותן דחיות, ועוצר לפני השמירה. התשובה היא `{ dryRun: true, version, receipts, warnings }`, בלי `production`. כל אישור ביצוע מוסיף `class`, הסיווג של הפעולה (`S` בטוחה, `D` מוחקת, `P` משנה את מי שיכול לגשת לעבודה, `$` מוציאה קרדיטים), ובמחיקה שאפשר לבטל, גם `restorable: true`. גם כאן אישורי הביצוע כוללים `detail` והתשובה כוללת `warningDetails`, כמו בהחלה.

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

```json
{ "ops": [], "dryRun": true }
```

רק תשובה שכוללת `dryRun: true` היא ״כן״. כל דבר אחר הוא ״לא״: אמרו לאדם שאי אפשר לראות כאן תצוגה מקדימה, ואל תשלחו את האצווה. בדקו את הסימון גם בתשובה של התצוגה המקדימה האמיתית, כי בפריסה שמתעדכנת, ייתכן שהבקשה הבאה תטופל בשרת אחר. תשובה עם `production` במקום הסימון פירושה שהאצווה הוחלה: אמצו אותה ואל תשלחו את האצווה שוב. ה-SDK מבצע את שתי הבדיקות בשבילכם וזורק `StudioPreviewUnavailable` או `StudioPreviewAppliedError`.

```ts
const preview = await client.studio.productions.ops(id, { ops, baseVersion, dryRun: true })
for (const r of preview.receipts) console.log(r.class, r.summary, r.restorable ?? false)
```

## יצירת תמונות סטילס וקליפים
יצירה היא הרצה ואחריה תשאול. הנתיב `POST /v1/studio/productions/:id/generate` עם `kind: "still"` או `kind: "clip"` ועם `shotId` שולח את המשימות, רושם אותן בהפקה כממתינות, וחוזר מיד עם `{ jobIds, lane?, deduped?, production? }`.

- **הבקשה נבנית מהשוט.** השרת מרכיב את הבקשה מהתוכנית, מהמראות, מהליהוק ומהוראות הבימוי של השוט. לכן קריאה מסקריפט ולחיצה בעורך יוצרות את אותה תמונה. השדה `overrides` משנה הרצה אחת בלי לשנות את השוט.
- **קודם אומדן מחיר.** הערך `dryRun: true` מתמחר את ההרצה ולא כותב כלום. התשובה היא `{ dryRun: true, provider, count, credits, lane? }`. הערך `credits: null` אומר שלמודל אין מחיר, כלומר המחיר לא ידוע, ולא שהוא חינם.
- **ניסיון חוזר בטוח.** השדה `clientRequestId`, מחרוזת של 8–128 תווים מתוך `A-Za-z0-9_.:-` שאתם יוצרים, הופך ניסיון חוזר לבטוח: אותו מזהה מחזיר את המשימות של הקריאה הראשונה עם `deduped: true`, ולא נגבים קרדיטים. לעולם אל תנסו שוב קריאה בתשלום בלעדיו.
- **הכניסו את התוצאות להפקה.** הנתיב `POST /v1/studio/productions/:id/reconcile` הופך משימות שהסתיימו לתוצאות ומנקה משימות שנכשלו. הוא מחזיר `{ landed, pending, failed, warnings, production, version }`. בקשת `GET` אף פעם לא כותבת, ולכן קראו ל-`reconcile` כשאתם מחכים למשהו.
- **השיטה של הקליפ נבחרת בשבילכם.** בקליפ, נתיב המודל (`generate-video` או `text-to-video`) נקבע לפי הקלטים של השוט, וחוזר כ-`lane`. השדה `mode` רק אומר מאיזו קבוצת קלטים לביים (`"start"` או `"references"`).

**curl**

```bash
curl -X POST https://app.nodaro.ai/v1/studio/productions/5d8f2b4a-7c1e-4a9d-8b3f-2e6c9a1d4f7b/generate \
  -H "Authorization: Bearer $NODARO_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "kind": "still", "shotId": "shot-2", "count": 2, "clientRequestId": "still-shot-2-take-1" }'

curl -X POST https://app.nodaro.ai/v1/studio/productions/5d8f2b4a-7c1e-4a9d-8b3f-2e6c9a1d4f7b/reconcile \
  -H "Authorization: Bearer $NODARO_API_KEY"
```

**TypeScript SDK**

```ts

const quote = await client.studio.productions.generateStill(id, 'shot-2', { count: 2, dryRun: true })
if (isStudioGenerateEstimate(quote)) console.log(quote.credits)

const run = await client.studio.productions.generateStill(id, 'shot-2', {
count: 2,
clientRequestId: crypto.randomUUID(),
})
// ...once the jobs finish:
const { landed, pending } = await client.studio.productions.reconcile(id)
```

נתיבי המדיה האחרים עובדים על שוט אחד:

| נתיב | גוף הבקשה | מה הוא עושה |
| --- | --- | --- |
| `frame` | `{ shotId, mode?, timestamp?, target?, clientRequestId? }` | שולף פריים מקליפ. ממתין כמה שניות ומחזיר `{ production, url? }`. |
| `voice` | `{ shotId, text, voiceId?, voiceType?, ttsProvider?, delivery?, clientRequestId? }` | מקריא שורה על גבי השוט. ממתין ומחזיר `{ production }`. |
| `revoice` | `{ shotId, plan, clientRequestId? }` | מחליף את הקולות בקליפ של השוט. מחזיר `{ jobId, production }`; התוצאה נכנסת להפקה דרך `reconcile`. |
| `music` | `{ prompt, duration?, instrumental?, vocalGender?, model?, clientRequestId? }` | יוצר את הפסקול. מחזיר `{ jobId, production }`; התוצאה נכנסת להפקה דרך `reconcile`. |

כל הנתיבים בתשלום כאן מקבלים `clientRequestId`, כולל `frame` ו-`voice`.

### קליפים מקושרים וצילומים חוזרים
יש פריסות שתומכות בקליפים שנעים בין שני פריימים שנבדקו. בדקו קודם את `GET /v1/studio/productions/capabilities`: השדות `operations.generateLinkedClips` ו-`operations.retakeLinkedClips` מציינים אם הם זמינים.

- **קבלו אומדן מחיר לקליפ מקושר.** הנתיב `generate` עם `kind: "clip"`, עם ה-`shotId` ועם `dryRun: true` בודק את שני הפריימים ומחזיר אומדן מחיר עם `inputHash`. כששולחים את הבקשה האמיתית, החזירו אותו כ-`expectedInputHash`. אם ההגדרות או הפריימים השתנו בינתיים, הנתיב מחזיר `409 sequence_quote_changed` לפני שהוא שולח משהו.
- **בצעו צילום חוזר של קליפ.** הוסיפו `retakeResultKey` כדי לקבל אומדן מחיר לטייק חדש של קליפ מקושר קיים, עם ההגדרות והפריימים המקוריים שלו. אחר כך שלחו את הבקשה עם ה-`inputHash` מהאומדן ועם `clientRequestId` חדש. אל תשלחו `mode`, `overrides` או `count`. אי אפשר לבצע צילום חוזר של טייק שהבקשה המקורית שלו או הפריימים שלו לא נשמרו.

## מבריף להפקה
### קריאת המדריך
הנתיב `GET /v1/studio/productions/skill` מחזיר `{ skill, catalog, schema, operating, generatedFrom }`: איך כותבים תוכנית, כל בורר, מודל וערך מותר, ה-JSON Schema של התוכנית ורשימת הפעולות.

### בדיקת תוכנית
הנתיב `POST /v1/studio/productions/validate` עם `{ plan }` מחזיר `{ valid, errors, warnings, summary? }`. תקנו את השגיאה בכל `path`, עד שהשדה `valid` הוא `true`. השמות שבליהוק נבדקים מול הספרייה שלכם.

### יצירת ההפקה
הנתיב `POST /v1/studio/productions` עם `{ name?, plan? }` יוצר את ההפקה ומכניס אליה את התוכנית. הנתיב `POST …/:id/import` עם `{ plan, mode: "append" }` מוסיף את השוטים של תוכנית להפקה קיימת. אף אחד מהם לא יוצר מדיה.

### או לתת לבמאי לכתוב את התוכנית
הנתיב `POST …/:id/describe` עם `{ brief, llmModel, mode?, label?, clientRequestId? }` מתחיל הרצה של הבמאי ומחזיר `{ jobId, production }`. כשהמשימה מסתיימת, קראו ל-`reconcile` כדי להכניס להפקה את השוטים שהבמאי ניסח.

```ts
const { production } = await client.studio.productions.create({ name: 'The Lighthouse' })
const { jobId } = await client.studio.productions.describe(production.id, {
brief: 'A keeper, a storm, and a light that will not start.',
llmModel: 'gpt-5-mini',
mode: 'replace',
})
// poll jobId with client.jobs.getStatus, then:
await client.studio.productions.reconcile(production.id)
```

## תכנון הייצוא
הנתיב `GET /v1/studio/productions/:id/export-plan` מחזיר לפי הסדר את השלבים שמרכיבים את הסרט: מיזוגי האודיו של כל שוט, האיחוד וגימור אופציונלי ב-4K עם `upscale`. הנתיב לא מריץ כלום.

```json
{
"canExport": true,
"steps": [
{
"id": "voice-shot-2",
"node": "merge-video-audio",
"label": "Voice over shot 2",
"credits": 20,
"params": { "videoUrl": "https://cdn.nodaro.ai/studio/shot-2.mp4", "audioUrl": "https://cdn.nodaro.ai/studio/vo.mp3" }
},
{
"id": "combine",
"node": "combine-videos",
"label": "Join 2 shots",
"credits": 4,
"params": {
"videoUrls": [{ "fromStep": "voice-shot-2" }, "https://cdn.nodaro.ai/studio/shot-3.mp4"],
"transition": "cut",
"audioMode": "keep"
}
}
],
"resultStepId": "combine",
"estimate": 24,
"unpriced": []
}
```

הריצו את השלבים לפי הסדר עם נתיבי הרכיבים הרגילים, כמו אלה של הרכיבים [מיזוג וידאו ואודיו](https://nodaro.ai/docs/nodes/video/merge-video-audio) ו[שילוב סרטונים](https://nodaro.ai/docs/nodes/video/combine-videos). ערך `{ "fromStep": "…" }` הוא הפלט של שלב קודם: החליפו אותו בכתובת שהשלב הזה הפיק. אחר כך רשמו את הקובץ המוגמר בהפקה כגרסת עריכה, עם הפעולה שמוסיפה גרסת עריכה.

הערך של `canExport` הוא `false` כשאין מה להרכיב, כלומר כשיש פחות משני קליפים. השדה `estimate` הוא `null` כשלשלב כלשהו אין מחיר, והשדה `unpriced` מציין את המודלים האלה, כי סכום חלקי היה מציג עלות נמוכה מהעלות בפועל.

## שיתוף והעתקה של הפקה
- **שיתוף.** הנתיב `POST …/:id/share` עם `{ shared: true }` מפעיל את קישור השיתוף לקריאה בלבד; הערך `{ shared: false }` או הנתיב `POST …/:id/unshare` מכבים אותו. רק הבעלים או מנהל של סביבת העבודה יכולים לשנות את השיתוף (אחרת הבקשה נדחית עם `403 forbidden`). כשהשדה `operations.revisionedSharing` זמין, הוסיפו `expectedVersion` כדי לקשור את השינוי לגרסה שבדקתם; אם בינתיים הייתה עריכה חדשה יותר, הבקשה נדחית עם `409 workflow_conflict`.
- **אפשרו עותקים לעריכה.** עם `operations.editableSharedCopies`, הבעלים יכולים לשלוח גם `allowEditableCopy: true`, יחד עם `shared: true` ו-`expectedVersion`. אז צופים מחוברים שנכנסים דרך הקישור יכולים להעתיק את התוכנית, הפרומפטים, תיאורי הליהוק, הקלטים השמורים והיסטוריית הטייקים. הפח והערות הבדיקה הפרטיות לא מועתקים, וביטול השיתוף עוצר עותקים חדשים.
- **העתקה.** הנתיב `POST …/:id/clone` עם `{ name? }` מעתיק הפקה כפי שאתם רואים אותה. העותק פרטי בהתחלה ולא בארכיון, והוא משתמש באחסון שלכם.

## ייצוא ציר זמן לתוכנת עריכה
הנתיב `POST /v1/freecut-export` הופך ציר זמן של קליפי סצנות לקובץ פרויקט עריכה, כדי שתוכלו להשלים את העריכה בתוכנת עריכה חיצונית. הנתיב כותב קובץ FreeCut JSON (`freecut-v1`) או Final Cut Pro XML (`fcpxml-v1.10`) לאחסון שלכם ומחזיר את הכתובת שלו. הייצוא לא עולה קרדיטים, והנתיב מקבל עד 10 בקשות לדקה. כמו כל השאר בדף הזה, הוא דורש Nodaro Cloud; מהדורות אחרות מחזירות `404`.

**curl**

```bash
curl -X POST https://app.nodaro.ai/v1/freecut-export \
  -H "Authorization: Bearer $NODARO_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
"format": "json",
"name": "The Lighthouse, cut 1",
"timeline": {
"musicAssetUrl": "https://cdn.nodaro.ai/studio/score.mp3",
"scenes": [
{
"sceneEntityId": "scene-1",
"compositeUrl": "https://cdn.nodaro.ai/studio/scene-1.mp4",
"shots": [{ "shot_id": "s1", "duration_seconds": 4 }]
},
{
"sceneEntityId": "scene-2",
"compositeUrl": "https://cdn.nodaro.ai/studio/scene-2.mp4",
"shots": [{
"shot_id": "s2",
"duration_seconds": 6,
"cut_decision": { "in_offset_sec": 0, "out_offset_sec": 0.5, "transition_to_next": "dissolve" }
}]
}
]
}
}'
```

**TypeScript SDK**

```ts
const file = await client.request('POST', '/v1/freecut-export', {
body: { format: 'json', name: 'The Lighthouse, cut 1', timeline },
})
```

```json
{
"url": "https://cdn.nodaro.ai/exports/7b2d4f6a/freecut-3c5e7a9b-1d2f-4a6c-8e3b-5f7a9c1e2d4b.json",
"format": "json",
"assetId": "1e3a5c7b-9d2f-4b4a-8c6e-5f7d9b1a3c2e"
}
```

השדה `assetId` הוא הרשומה של הקובץ בספרייה שלכם, או `null` כשאי אפשר היה ליצור את הרשומה הזו; ה-`url` תקף בכל מקרה.

| שדה | מה יש בו |
| --- | --- |
| `format` | נדרש. הערך `json` ל-FreeCut JSON, או `fcpxml` ל-Final Cut Pro XML. |
| `timeline` | נדרש. ציר הזמן, כמפורט בהמשך. |
| `name` | שם לזיהוי, עד 200 תווים. |
| `timeline.scenes` | נדרש. סצנה אחת לפחות, לפי סדר ההשמעה. כל סצנה הופכת לקליפ אחד ברצועת הווידאו. |
| `timeline.musicAssetUrl` | רצועת המוזיקה. מחרוזת ריקה, ברירת המחדל, משמיטה אותה. |
| `timeline.narrationAssetUrl` | רצועת קריינות, שמוצבת ברצועת אודיו נפרדת. |
| `timeline.fadeOutDurationSec` | עמעום היציאה בסוף המוזיקה, 0.8 שנייה כברירת מחדל. ב-FreeCut JSON בלבד. |
| `scene.sceneEntityId`, `scene.compositeUrl` | נדרשים. המזהה של הסצנה והקליפ הממוזג שלה. |
| `scene.shots` | נדרש. שוט אחד לפחות, `{ shot_id, duration_seconds, cut_decision? }`. סכום האורכים של השוטים הוא האורך של הסצנה. |
| `cut_decision.in_offset_sec`, `cut_decision.out_offset_sec` | נדרשים ב-`cut_decision`. הקיצוץ בהתחלה (נלקח מהשוט הראשון של הסצנה) ובסוף (נלקח מהשוט האחרון שלה). |
| `cut_decision.transition_to_next` | נדרש ב-`cut_decision`. המעבר לסצנה הבאה: `hard_cut`, `dissolve`, `match_cut` או `overlap`. |
| `cut_decision.transition_duration_sec` | מחליף את אורך ברירת המחדל של המעבר: 0 ל-`hard_cut` ול-`match_cut`, 0.5 שנייה ל-`dissolve`, שנייה אחת ל-`overlap`. |

המעברים `dissolve` ו-`overlap` יוצרים חפיפה בין שני הקליפים באורך המעבר; המעברים `hard_cut` ו-`match_cut` מצמידים אותם זה לזה. כשלאף שוט אין `cut_decision`, הייצוא הוא שרשור פשוט: קליפ אחד לכל סצנה, זה אחרי זה, עם חיתוכים חדים ומוזיקה לאורך כל ציר הזמן. קיצוצים בתוך סצנה לא מוחלים, כי כל קליפ של סצנה כבר ממוזג.

## רשומות לשיתוף ולרימיקס
**רשומת שוט** שומרת את המצב של שוט שבניתם: הבחירות בבוררים, הפרומפטים, מודלי היעד, הנכסים שאוזכרו עם `@` והתוצאות. הרשומה נמצאת מאחורי מזהה קצר שאי אפשר לנחש, שעליו מבוססים קישורי השיתוף `/s/:id` ורימיקסים בלחיצה אחת. הנתיבים האלה נפרדים מההפקות.

| שיטה | נתיב | אימות | מה היא עושה |
| --- | --- | --- | --- |
| `POST` | `/v1/shots` | אסימון Bearer | יצירת רשומה. התגובה היא `{ id }`. |
| `GET` | `/v1/shots/:id` | אין | קריאת רשומה. בקשה לרשומה פרטית ממי שהוא לא הבעלים שלה נדחית עם `404`. יש מגבלה לכל כתובת IP. |
| `PATCH` | `/v1/shots/:id` | הבעלים | עדכון של כל שדה, כולל `visibility`. |
| `DELETE` | `/v1/shots/:id` | הבעלים | מחיקת הרשומה. |

גוף הבקשה כולל את `mode` (`single`, `multi-shot`, `frame-to-motion` או `storyboard`), את `selectionState` (הערך שנבחר בכל בורר, כ-`{ pickerNodeType: valueId }` או `{ pickerNodeType: { field: valueId } }`), ואופציונלית גם את `freeText`, `negativePrompt`, `assembledPrompt`, `perModelPrompts`, `models`, `entityRefs` ו-`resultUrls`. השדה `visibility` הוא `private` כברירת מחדל; הגדירו אותו ל-`public` כדי שאפשר יהיה לשתף את הרשומה.

השדה `resultUrls` מקבל רק כתובות URL ציבוריות פשוטות של `http` או `https`. כתובות חתומות נדחות, כך שאסימון אף פעם לא דולף לרשומה משותפת. לכל רשומה יש `schemaVersion`. כשרשומה מציינת פריט בקטלוג שכבר לא קיים, דלגו עליו והוסיפו הערה, במקום להכשיל את הרימיקס.

**curl**

```bash
curl -X POST https://app.nodaro.ai/v1/shots \
  -H "Authorization: Bearer $NODARO_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
"mode": "single",
"selectionState": { "mood": "melancholy", "lighting": { "timeOfDay": "blue-hour" } },
"freeText": "a woman at a bus stop",
"models": ["nano-banana-pro"],
"visibility": "public"
}'
```

**TypeScript SDK**

```ts
const { id: shotId } = await client.shots.create({
mode: 'single',
selectionState: { mood: 'melancholy', lighting: { timeOfDay: 'blue-hour' } },
freeText: 'a woman at a bus stop',
})
await client.shots.update(shotId, { visibility: 'public' })
const { shot } = await client.shots.get(shotId)
```

**CLI**

```bash
nodaro shots create --file shot.json --visibility public
nodaro shots get <id> --json
nodaro shots delete <id>
```

## שימוש דרך MCP
עוזרי AI עובדים על אותן הפקות דרך כלים כמו `get_studio_production_skill`, `validate_studio_plan`, `create_studio_production`, `edit_studio_production`, `generate_studio_still`, `generate_studio_clip` ו-`plan_studio_export`. קריאה של הפקה עם הרשאת כתיבה גם מכניסה להפקה משימות שהסתיימו. ראו את הדף [הפקות Studio דרך MCP](https://nodaro.ai/docs/mcp/studio-productions).

## שגיאות
| סטטוס | קוד | משמעות |
| --- | --- | --- |
| `400` | `validation_error` | גוף הבקשה או התוכנית שגויים. השדה `path` מציין את השדה השגוי; בנתיב הייצוא, השדה `issues` מפרט את הבעיות. |
| `400` | `op_invalid`, `op_target_missing` | ב-`…/ops`: אחת הפעולות הייתה שגויה, והשדה `opIndex` מציין אותה. שום דבר לא נכתב. תקנו אותה ושלחו את האצווה שוב. |
| `402` | `insufficient_credits` | אין בחשבון מספיק קרדיטים ליצירה. |
| `403` | `forbidden` | רק הבעלים או מנהל של סביבת העבודה יכולים לשנות את השיתוף. |
| `404` | `not_found` | אין הפקה כזו שזמינה לכם, או שהפריסה לא מספקת הפקות. |
| `404` | `op_target_missing` | בנתיב יצירה או מדיה: השוט, התוצאה או התפקיד שציינתם לא נמצאים בהפקה. |
| `409` | `workflow_conflict` | נשלח `strict: true` או `expectedVersion`, וההפקה השתנתה. קבלו אותה שוב והחילו את השינויים מחדש. |
| `409` | `production_busy` | ההפקה המשיכה להשתנות בזמן הכתיבה. קבלו אותה מחדש ונסו שוב. |
| `409` | `sequence_quote_changed` | ההגדרות או הפריימים של קליפ מקושר השתנו מאז האומדן. בקשו אומדן מחיר חדש. |
| `413` | `storage_exceeded` | החשבון חרג ממגבלת האחסון שלו. |
| `429` | `rate_limit_exceeded` | יותר מ-10 ייצואים של ציר זמן בדקה. המתינו את הזמן שמופיע ב-`Retry-After`. |

ב-SDK השגיאות האלה מגיעות כשגיאות עם טיפוס: `StudioOpError` (עם `opIndex`), `WorkflowConflictError` לשני קודי ה-`409` של כתיבה, `InsufficientCreditsError`, `StorageExceededError` ו-`NotFoundError`.

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

### מה זו הפקת Studio?

הפקת Studio היא סרט שבונים ב-Nodaro Studio. ההפקה היא תהליך שבהגדרות שלו נמצאת רשימה של שוטים לפי הסדר, שהעורך קורא להם סצנות. לכל שוט יש תמונת סטילס של הפריים, קליפ מונפש אופציונלי, והתוכנית, המראות, הליהוק והקול שיצרו אותם.

### למה צריך לקרוא ל-reconcile אחרי יצירה?

בהפקת Studio, קריאת יצירה חוזרת מיד, והיצירה רצה ברקע. נקודת הקצה POST /v1/studio/productions/:id/reconcile הופכת את המשימות שהסתיימו לתוצאות בשוטים שלהן. בקשת GET רגילה אף פעם לא כותבת, ולכן משימה שהסתיימה נשארת ממתינה עד שקוראים ל-reconcile.

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

ביצירה בתשלום בהפקת Studio, שלחו את השדה clientRequestId, מחרוזת של 8–128 תווים שאתם יוצרים בעצמכם. שליחה חוזרת של אותו מזהה מחזירה את המשימות שהקריאה הראשונה התחילה, עם הסימון deduped, ולא נגבים עליה קרדיטים.

### האם אפשר לראות תצוגה מקדימה של מה שאצוות פעולות תעשה?

כן, כששולחים את אצוות הפעולות עם השדה dryRun שמוגדר ל-true. קודם שלחו אצווה ריקה עם dryRun, כדי לוודא שהשרת תומך בתצוגות מקדימות, כי שרת שלא תומך בהן יחיל את האצווה.

### האם אפשר לייצא ציר זמן של Studio ל-Final Cut Pro או לתוכנת עריכה אחרת?

כן. נקודת הקצה POST /v1/freecut-export הופכת ציר זמן של קליפי סצנות לקובץ FreeCut JSON או Final Cut Pro XML באחסון שלכם. הייצוא לא עולה קרדיטים.
