דילוג לתוכן
תיעוד Nodaro
תיעודמדריך הרכיביםמודליםתבניותסוכני AI (MCP)מפתחיםאירוח עצמימחקרהשוואות
הטמעות

הטמעת מיני־אפליקציה

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

הטמעת מיני־אפליקציה פירושה הרצה של אפליקציית Nodaro שכבר פורסמה, מתוך המוצר שלכם. אפשר להדביק בדף את קוד ההטמעה של האפליקציה, או לבנות טופס משלכם על ה-REST API: לקרוא את הקלטים של האפליקציה, להתחיל הרצה, לבדוק את הסטטוס שלה מדי כמה שניות ולהציג את התוצאה. האפליקציה עצמה נשארת ב-Nodaro; המוצר שלכם אוסף את הקלטים ומציג את הפלטים.

שתי דרכים להטמיע

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

הדבקת קוד ההטמעה

פתיחת הגדרות ההטמעה של האפליקציה

ב-Nodaro, פתחו את הדף מיני־אפליקציות, בחרו בלשונית המיני־אפליקציות שלי ולחצו על הטמעה בכרטיס של האפליקציה.

אישור הדומיין שלכם

תחת דומיינים מורשים להטמעה, הקלידו את ה-origin של האתר שלכם, כגון https://example.com, ולחצו על הוספה. ההטמעה חסומה עד שמוסיפים לפחות דומיין אחד.

העתקת הקוד לדף שלכם

לחצו על העתקת קוד הטמעה והדביקו את התג בדף שלכם:

<iframe src="https://app.nodaro.ai/embed/your-app-slug" width="100%" height="600" frameborder="0" allow="clipboard-write"></iframe>

בניית ממשק משלכם

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

שלושה דברים שכדאי לדעת קודם

כתובת הבסיסhttps://app.nodaro.ai ב-Nodaro Cloud, או הכתובת של ההתקנה שלכם.
מזהה האפליקציהה-slug, כלומר החלק האחרון בכתובת ה-URL של האפליקציה שפורסמה. בכתובת https://app.nodaro.ai/app/my-cool-app, ה-slug הוא my-cool-app.
אופן ההרצהאסינכרוני. הנתיב POST /v1/app/{slug}/run מחזיר מיד runId, ואתם בודקים את הסטטוס מדי כמה שניות עם GET /v1/app/{slug}/runs/{runId} עד שהתוצאה מוכנה. ההרצה לא שולחת אליכם קריאה חוזרת.

שתי נקודות קצה ציבוריות, שלא דורשות אסימון, נותנות לכם את כל מה שצריך לטופס:

  • הנתיב GET /v1/app/{slug} מחזיר את הקלטים ואת המטא־נתונים של האפליקציה.
  • הנתיב GET /v1/nodes/{type} מחזיר את השדות של סוג רכיב אחד.

שלב 1: קריאת הקלטים של האפליקציה

curl https://app.nodaro.ai/v1/app/<slug>

השדות שחשובים לממשק שלכם:

{
  "id": "uuid",
  "name": "Headline Generator",
  "description": "...",
  "iconUrl": "https://...",
  "version": 3,                        // the latest version
  "estimatedCredits": 5,               // the listed price: a run, with any Render final after it
  "perMinuteCredits": 0,               // per minute of the recording the app is given; 0 when the price does not depend on it
  "perItemCredits": 0,                 // per item beyond the saved items of a list the user fills; 0 when none
  "maxEstimatedCredits": 9,            // "up to": the most a run can cost over the user's choices (with
  "maxPerMinuteCredits": 0,            // maxPerMinuteCredits and maxPerItemCredits); present only when it
  "maxPerItemCredits": 0,              // differs from the "from" figures above
  "estimatedCreditsIsEstimate": true,  // present when the price holds a metered LLM call's estimate: show "~5"
  "runEstimatedCredits": 5,            // the run alone: the listed price without its Render final part
  "runPerMinuteCredits": 0,            // ...and its per-minute and per-item parts
  "runPerItemCredits": 0,
  "editPlanPerMinute": false,          // true when an Edit Plan node is charged per started minute of the recording
  "maxRunsPerUserPerDay": null,        // or a number
  "thumbnailNodeId": "node-abc",       // the node whose output is the main result
  "snapshotNodes": [                   // the workflow's nodes
    {
      "id": "node-abc",
      "type": "generate-image",        // use it in step 2
      "data": {
        "prompt": "default prompt",    // the current value of each field
        "aspectRatio": "1:1"
      }
    }
  ],
  "snapshotEdges": [ /* the connections, rarely needed by your interface */ ],
  "snapshotSettings": {
    "presentationSettings": {
      "inputItems": [                  // the form schema
        { "type": "field", "id": "item-1", "nodeId": "node-abc", "field": "prompt", "allowedValues": null, "maxLength": 1200 },
        { "type": "field", "id": "item-2", "nodeId": "node-abc", "field": "aspectRatio", "allowedValues": ["1:1", "16:9", "9:16"] }
      ]
    }
  },
  "versions": [{ "version": 3, "id": "...", "createdAt": "..." }]
}

השדה inputItems הוא הטופס שהמפרסם עיצב. עברו עליו, היכנסו לכל group ואספו את כל הפריטים מסוג field: זה הטופס שלכם.

typeאיך להציג אותו
fieldשדה בטופס. השתמשו ב-nodeId, ב-field, ב-allowedValues האופציונלי וב-maxLength האופציונלי, מגבלת התווים של שדה טקסט.
nodeתצוגת ברירת המחדל של הרכיב כולו. בטופס משלכם, דלגו על הפריט הזה או הציגו כל שדה של הרכיב. גם רכיב טקסט שנחשף כולו יכול לכלול maxLength.
outputתצוגה מקדימה חיה של תוצאה. התעלמו מהפריט הזה כשאתם בונים את הטופס, והציגו אותו אחרי ההרצה.
richtextטקסט Markdown קבוע שהמפרסם כתב. הציגו אותו כמו שהוא.
groupקבוצה עם items משלה. היכנסו אליה; אין קבוצות בתוך קבוצות.

שלב 2: בירור הסוג של כל שדה

השדה inputItems נותן לכם זוגות של nodeId ו-field, אבל לא את סוג השדה: טקסט, מספר או בחירה. כדי לדעת יותר, בדקו את התיאור של סוג הרכיב:

curl https://app.nodaro.ai/v1/nodes/generate-image
{
  "data": {
    "type": "generate-image",
    "label": "Generate Image",
    "category": "ai-image",
    "description": "...",
    "outputType": "image",
    "providers": ["nano-banana-pro", "gpt-image-2", "flux"],   // shortened
    "capabilities": ["supports-reference-image", "supports-negative-prompt"]
  }
}

לא לכל מתאר רכיב יש inputSchema מלא. כשהוא חסר, הסיקו מהו הפקד לפי הכללים האלה, בסדר הזה:

  1. לפריט הקלט יש allowedValues: רשימה נפתחת עם האפשרויות האלה.
  2. הערך הנוכחי ב-snapshotNodes[i].data[field] הוא בוליאני: מתג.
  3. הערך הנוכחי הוא מספר: שדה מספרי, או מחוון כששם השדה מסתיים ב-duration, ב-intensity, ב-strength, ב-scale או ב-temperature.
  4. הערך הנוכחי הוא מחרוזת, ושם השדה הוא prompt, description, text, content, caption או message: שדה טקסט מרובה שורות.
  5. הערך הנוכחי הוא כתובת URL, וסוג הרכיב מתחיל ב-upload-: העלאת קובץ ששולחת את כתובת ה-URL הציבורית של הקובץ.
  6. כל מקרה אחר: שדה טקסט של שורה אחת.

שמות השדות האלה מכסים את רוב השדות שאפליקציות חושפות:

שם השדהפקדהערות
prompt, negativePrompt, text, descriptionשדה טקסט מרובה שורות
model, provider, voice, style, toneרשימה נפתחתהאפשרויות מגיעות מ-allowedValues או מ-providers.
aspectRatioרשימה נפתחתבדרך כלל 1:1, 16:9, 9:16, 4:3 ו-3:4.
resolutionרשימה נפתחתבדרך כלל 1K, 2K ו-4K, או 720p, 1080p ו-4k.
qualityרשימה נפתחתבדרך כלל medium ו-high.
duration, nFrames, seed, temperatureמספר
enableTranslation, addAudio, headless, loopמתג
imageUrl, videoUrl, audioUrl, referenceImageכתובת URLהעלו קודם את הקובץ, ואז שלחו את הכתובת שלו.

דרך MCP, הכלי get_app_inputs מציין בשבילכם את הסוג של חלק מהשדות. מחוון שהאפליקציה חושפת חוזר כ-type: "number", עם ה-min, ה-max וה-step שלו. שדה שהמפרסם הגביל לרשימת ערכים חוזר כ-type: "select", עם ה-options האלה. שדה מההגדרות המהירות של הרכיב חוזר כ-select, כ-number או כ-boolean; ראו את הסעיף שדות של מודל ושל הגדרות מהירות. כל שדה אחר חוזר כ-type: "text", ולכן בדקו אותו בתיאור של הרכיב. קלט שטוח של מחוון מקבל מספר או מחרוזת מספרית.

מחוונים ומתגים. מחוון או מתג מתחילים מהערך השמור של הרכיב, ב-snapshotNodes[i].data[field]. כשאין לרכיב ערך שמור, האפליקציה מציגה את ברירת המחדל של השדה: הערך שרכיב חדש מקבל. הרצה שולחת שדה רק אחרי שהמשתמש שינה אותו. עד אז חל הערך של הרכיב עצמו. עשו את אותו הדבר בממשק שלכם: מלאו מראש את הפקד, ושלחו את השדה רק כשהמשתמש שלכם משנה אותו. הגדרות הקול של רכיב טקסט לדיבור מקבלות מספר או מחרוזת מספרית, וערך מחוץ לטווח של ההגדרה מוצמד לקצה הקרוב של הטווח.

שדות של מודל ושל הגדרות מהירות

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

  • המפתח הוא שדה הנתונים של הרכיב עצמו, כגון provider, v2vResolution, duration או draft. שלחו את הערך ב-inputOverrides[nodeId][field], בסוג שהרכיב שומר: הערך 8 בשדה של משך, ולא "8", והערך true בשדה של טיוטה.
  • רשימה קבועה נאכפת. חלק מההגדרות מציעות את אותם ערכים בלי קשר לשאר ההגדרות של הרכיב, כגון המודלים שרכיב מציע, מודל של Suno או יחס הגובה־רוחב של סצנת תלת־ממד. ערך מחוץ לרשימה נדחה עם 400 validation_error, וההודעה היא Invalid value for provider: …. Allowed: … (ערך לא תקין ל-provider: …. ערכים מותרים: …). המפרסם יכול לצמצם את הרשימות האלה עוד יותר עם allowedValues.
  • טווח נאכף. הגדרה שמקבלת כל מספר בטווח, כגון ההגדרה אורך של סצנת תלת־ממד או ההגדרה סף של רכיב מסכת וידאו, דוחה מספר מחוץ לטווח עם 400 validation_error.
  • חלק מהאפשרויות נקבעות לפי המודל. המודל של הרכיב קובע אילו רזולוציות, יחסי גובה־רוחב ומשכי זמן הגדרה מציעה. הכרטיס של האפליקציה מציע את האפשרויות של המודל שההרצה תשתמש בו, וכשהמודל מתחלף, אפשרות שהמודל החדש לא תומך בה מתחלפת באפשרות שהוא תומך בה. עשו את אותו הדבר בטופס שלכם: כלי ה-MCP get_app_inputs מפרט את הערכים של כל המודלים לשדה כזה, והשדה snapshotNodes[i].data.provider, או הערך שהטופס שלכם שולח, מציין איזה מודל רץ.
  • טיוטה מרונדרת ב-480p. במודלים של Seedance, הכרטיס של האפליקציה גם מגדיר את הרזולוציה ל-480p כשההגדרה טיוטה מופעלת, והשרת מרנדר טיוטה ב-480p בלי קשר לרזולוציה שנשלחה.

ניקוי שדה

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

בקלט שטוח דרך MCP או דרך ה-SDK, הערך null מושמט לפני שהוא מגיע לרכיב, ולכן חל הערך השמור של הרכיב. כדי לנקות שדה, השתמשו ב-inputOverrides.

שדות של משבצות Lottie

רכיב גרפיקת תנועה על מנוע Lottie יכול לחשוף כקלטים של האפליקציה את המשבצות שלו שיש להן שם, כגון צבעים, טקסטים ומספרים. הן מופיעות כפריטי שדה שה-field שלהם מתחיל ב-slot:, כגון slot:primaryColor. המשתמשים משנים אותן בכל הרצה תמורת 0 קרדיטים: כשאפליקציה חושפת שדות של משבצות, כל הרצה משתמשת שוב באנימציה שפורסמה ומחליפה רק את ערכי המשבצות.

ערך המשבצתפקדהערך לשליחה
צבע, כמערך RGBA של מספרים מ-0 עד 1פקד בחירת צבעמחרוזת הקסדצימלית, כגון "#00ff00"
מחרוזתשדה טקסטהמחרוזת
מספרמחווןהמספר

ערך של משבצת הוא לא שדה רגיל של רכיב: הוא נמצא ב-motionPlan.slotValues של הרכיב. כדי לקבוע משבצות דרך inputOverrides גולמי, החליפו את כל ה-motionPlan של הרכיב בעותק של התוכנית שפורסמה, שה-slotValues שלו כוללים את השינויים שלכם, עם צבעים כמערכי RGBA. עדכון חלקי של slotValues ימחק את שאר התוכנית. Nodaro עצמו וה-SDK בונים את זה בשבילכם.

{
  "inputOverrides": {
    "node-mg1": {
      "motionPlan": {
        // ...the published node's motionPlan, unchanged...
        "slotValues": { "primaryColor": [0, 1, 0, 1], "nameText": "Acme Inc." }
      }
    }
  }
}

קלטים של רכיב כתובת וידאו

רכיב כתובת וידאו יכול להיות קלט של אפליקציה, כך שהאפליקציה מקבלת פרק כקישור במקום כהעלאה. הכלי get_app_inputs מחזיר את השדה כ-type: "video", והקלט השטוח הוא הקישור:

{ "inputs": { "episode": "https://www.youtube.com/watch?v=VIDEO_ID" } }
  • מה השדה מקבל. קישור לדף שבו נמצא סרטון, או כל קישור http או https ציבורי אחר לסרטון. כשהקישור הוא לדף של סרטון, השרת מוריד את הסרטון, וכל קישור אחר משמש כמו שהוא. ערך שהוא לא קישור לאינטרנט, או שמצביע על כתובת פרטית, נדחה עם 400 locked_field לפני שמשהו רץ ולפני כל חיוב. קישור לרכיב כתובת וידאו שהאפליקציה לא חושפת נדחה באותה דרך.
  • בדף של האפליקציה. קישור לדף של סרטון מציג את תיבת זכויות התוכן מתחת לשדה. מי שמשתמש באפליקציה מסמן את התיבה כדי לאשר את ההצהרה לקישור שלו, ושום דבר לא נמשך לפני כן. כשהקישור משתנה, הסימון מתבטל. כשרק רכיב מתכון תוכן קורא את הקישור, שום דבר לא מיובא ולא מוצגת תיבה. רכיב דיבוב מדבב את הקובץ, ולכן קישור שרכיב דיבוב קורא מורד כמו קישור שרכיב צופה בו.
  • השרת מוריד את הסרטון. לפני שהרכיב הראשון רץ, השרת מושך את מה שהרכיבים שאחרי הקישור קוראים. רכיב שצופה בסרטון מקבל את הסרטון. הרכיבים תמלול וקאבר Suno מקבלים רק את האודיו. רכיב דיבוב מקבל את הסרטון, אלא אם הוא מדבב קישור משלו, מהשדה קישור מקור. רכיב מתכון תוכן מקבל את כתובת הדף, ולכן שום דבר לא נמשך בשבילו.
  • סרטון ארוך צריך קטע. מאתר ש-Nodaro קורא בו קודם את אורך הסרטון, סרטון של 4 דקות או יותר, או סרטון שאי אפשר לקרוא את האורך שלו, מורד רק כקטע. ציינו את הקטע ברכיב, ב-inputOverrides, בשניות: { "<nodeId>": { "sectionStartSec": 600, "sectionEndSec": 1200 } }. בלי קטע, ההרצה נדחית לפני כל חיוב. גם שידור חי נדחה, וכשההורדה נכשלת, ההרצה נדחית והסיבה מצוינת.
  • דריסות גולמיות. ב-inputOverrides, הקישור נכנס לשדה youtubeUrl של הרכיב.
  • זכויות תוכן. המשיכה של השרת נחשבת לייבוא של הקישור. שלחו ב-inputOverrides, בשדה rightsConfirmationId של הרכיב, את האישור לקישור הזה ולהרצה הזו, שמקבלים מ-POST /v1/content-rights/confirm. אישור אחד מאפשר הורדה אחת בתוך שעה, ולכן הוא אף פעם לא נשמר עם קלטים שמורים. הרצה בלי אישור נדחית רק כשהפריסה דורשת אישור. ראו בדף ה-API זכויות תוכן.
  • מחיר. השלבים שאחרי הקישור, שהחיוב עליהם נקבע לפי אורך הסרטון, מתומחרים לפי דקה של ההקלטה הזו, ב-perMinuteCredits.

קלטים של סרטון UGC

לאפליקציה שבנויה על רכיבי סרטון UGC, ב-Nodaro Cloud, יש שני קלטים משלה: כרטיס היוצר ואורך הסרטון.

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

{
  "inputOverrides": {
    "<creator node id>": {
      "source": "photo",
      "gender": "woman",
      "photoUrl": "https://example.com/runner.jpg"
    }
  }
}
  • השדה source הוא sampled (לאפשרות יוצר/ת בינה מלאכותית) או photo.
  • השדה gender הוא woman או man.
  • השדה photoUrl הוא כתובת URL ציבורית של תמונה, לאפשרות התמונה שלי בלבד.

כלי ה-MCP get_app_inputs מחזיר את הכרטיס כשלושה מפתחות: המפתח של היוצר עצמו ל-source, ואותו מפתח עם _gender ועם _photo_url, ששניהם אופציונליים. הרצת אפליקציה אף פעם לא מחכה שייבחר יוצר: היא מרנדרת יוצר אחד. הרצה באפשרות התמונה שלי שלא נותנת תמונה של מי שמריץ אותה נדחית עם 400 ugc_photo_required לפני שמשהו רץ ולפני כל חיוב.

אורך הסרטון. ההגדרה אורך הסרטון (שניות) של רכיב תסריט UGC, targetDurationSec, יכולה להיות פריט field. ההגדרה היא מחוון מ-4 עד 300 שניות, או האורכים שהאפליקציה מאפשרת ב-allowedValues, כגון [15, 20, 25]. ב-inputOverrides, שלחו אותה כמספר: 20, ולא "20". קלט שטוח מקבל את שתי הצורות.

הרצה לא יכולה לקבוע שום דבר אחר ברכיב UGC. כל שדה אחר ברכיב UGC, כגון תוצאה שנשמרה, תסריט או קליפ, נדחה עם 400 locked_field. ראו את הסעיף סרטון UGC באפליקציה בדף אפליקציות (מיני־אפליקציות).

שלב 3: בניית הטופס

// Adapt to your framework.
type FormField = {
  nodeId: string
  field: string
  label: string                     // "aspectRatio" becomes "Aspect ratio"
  control: "text" | "textarea" | "number" | "toggle" | "select" | "upload"
  options?: Array<string | number | boolean>
  defaultValue: unknown
}

async function buildForm(slug: string, baseUrl: string): Promise<FormField[]> {
  const app = await fetch(`${baseUrl}/v1/app/${slug}`).then((r) => r.json())
  const nodesById = new Map(app.snapshotNodes.map((n) => [n.id, n]))

  const fields: FormField[] = []
  const walk = (items) => {
    for (const it of items ?? []) {
      if (it.type === "group") walk(it.items)
      if (it.type !== "field") continue
      const node = nodesById.get(it.nodeId)
      const current = node?.data?.[it.field]
      fields.push(toFormField(it, node, current))
    }
  }
  walk(app.snapshotSettings?.presentationSettings?.inputItems ?? [])
  return fields
}
  • השתמשו במזהה הרכיב כמפתח של הערכים, לא בתווית. גוף הבקשה של ההרצה הוא { "inputOverrides": { "<nodeId>": { "<field>": value } } }.
  • Nodaro אוכף את allowedValues. ערך מחוץ לרשימה נדחה עם 400 validation_error. כך גם ערך מחוץ לרשימה הקבועה של שדה של הגדרה מהירה.
  • Nodaro אוכף את maxLength. טקסט ארוך מהמגבלה נדחה עם 400 input_too_long. הוסיפו maxlength לשדה הטקסט והציגו מונה תווים. בקלט של טקסט להקראה, המחיר של האפליקציה מחושב לפי המגבלה הזו.
  • Nodaro אוכף מגבלות של בוררים. המפרסם יכול להגביל בורר, כגון הבוררים סביבה, מצב־רוח או אדם, לחלק מהקטלוג שלו, ב-snapshotSettings.presentationSettings.cardMeta[nodeId]. בורר עם ממד אחד משתמש ב-pickerAllowedValues, ובורר עם כמה ממדים משתמש ב-pickerAllowedValuesByField ({ field: [ids] }). ערך מחוץ לרשימה נדחה עם 400 validation_error בכל הרצה של האפליקציה, כולל הרצות דרך ה-API. כדי לבטל בחירה, השמיטו את השדה או שלחו ערך ריק. אל תשלחו את ברירת המחדל של הקטלוג.
  • מלאו מראש את ערכי ברירת המחדל מ-snapshotNodes[i].data, כדי שגם משתמש שלא משנה כלום יקבל הרצה הגיונית.
  • הציגו את estimatedCredits, כדי שהמשתמשים יידעו מה העלות לפני שהם מריצים. כש-perMinuteCredits או perItemCredits שונים מ-0, המחיר גדל עם אורך ההקלטה או עם מספר הפריטים ברשימה: הציגו חלק קבוע ועוד החלקים האלה. השדה estimatedCredits כולל גם רינדור סופי, אם יש כזה, והשדה runEstimatedCredits, עם החלקים שלו לדקה ולפריט, הוא מחיר ההרצה בלבד. כשהשדה maxEstimatedCredits מופיע, המחיר נקבע לפי מה שהמשתמש בוחר: הציגו אותו כטווח, כגון ״החל מ-5 ועד 9״. המספר שאחרי ״עד״ הוא המחיר המרבי של הרצה על פני כל השדות שהאפליקציה חושפת: לפי הערכים שהמפרסם התיר, היכן שהוא הגדיר אותם, לפי האפשרויות של כל מודל ולפי קליפ ייחוס שהמשתמש מספק, באורך המרבי שלו. גרסה שפורסמה לפני שהטווח הזה נוסף לא כוללת שדות max*, עד שמפרסמים אותה שוב. כש-estimatedCreditsIsEstimate הוא true, המחיר כולל אומדן של קריאת LLM. הקריאה הזו מחויבת לפי הטוקנים שהיא משתמשת בהם בפועל, ולכן הציגו את המחיר כ-~5, ולא כ-5.
  • הציגו את המגבלה היומית כש-maxRunsPerUserPerDay מוגדר, כגון ״נוצלו היום 2 מתוך 5 הרצות״, כדי שתשובת 429 לא תפתיע.

שלב 4: התחלת הרצה ובדיקת הסטטוס שלה

התחילו את ההרצה עם אסימון:

curl -X POST https://app.nodaro.ai/v1/app/<slug>/run \
  -H "Authorization: Bearer $NODARO_API_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "inputOverrides": {
      "node-abc": { "prompt": "a cat astronaut", "aspectRatio": "16:9" }
    }
  }'

התשובה היא 202 Accepted:

{ "executionId": "exec-uuid", "runId": "run-uuid", "status": "pending" }
שדה אופציונלי בגוף הבקשהמה הוא עושה
versionמריץ גרסה מסוימת של האפליקציה. ברירת המחדל היא הגרסה האחרונה.
inputsהקלטים של האפליקציה כמיפוי שטוח של שמות קלטים לערכים, כמו שה-SDK וה-CLI שולחים אותם. השדה inputOverrides מוחל מעל inputs, שדה אחר שדה, וגובר בכל שדה ששניהם קובעים. שדה מספרי, כגון מחוון שהאפליקציה חושפת, מקבל מספר או מחרוזת מספרית: הערך "0.4" מגיע כ-0.4.
runIdמשייך את ההרצה לטיוטת הרצה שיצרתם קודם. בבנייה ראשונה אפשר להתעלם ממנו.

השדה inputOverrides יכול לקבוע גם שדות של רכיבים שהאפליקציה לא חושפת, כגון promptPrefix; ראו את הדף טקסט לפני ואחרי הפרומפט. הוא אף פעם לא יכול לשנות את היעד של רכיב ששולח החוצה או את המקום שממנו הוא מביא נתונים, כגון הכתובת של רכיב פלט Webhook, או איזה תהליך רכיב תת־תהליך מריץ: הרצה כזו נדחית עם 400 locked_field.

אפליקציה עם תוספת מחיר של היוצר. ב-Nodaro Cloud, הרצה של אפליקציה עם תוספת מחיר של היוצר נבדקת מול היתרה של מי שמריץ אותה, לפני שמשהו נכתב או רץ. היתרה צריכה לכסות את מחיר ההרצה המוצג של האפליקציה יחד עם תוספת המחיר:

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

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

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

החיוב הסופי על תוספת המחיר נעשה כשההרצה מסתיימת, וממי שמשלם נגבה רק מה שיש לו באותו רגע. כלומר, לכל היותר היתרה של מי שמריץ, שאף פעם לא יורדת מתחת לאפס בגלל התוספת, או לכל היותר היתרה הפנויה בתקציב של סביבת העבודה. היוצר מקבל בדיוק את הסכום שנגבה. את השאר לא גובים מאף אחד ולא משלמים לאף אחד. ברשומת הרווחים של היוצר נשמרת תוספת המחיר שנדרשה (flatFee, percentFee) לצד מה שנגבה בפועל (totalEarned). ראו את דף ה-API סביבות עבודה וארגונים.

אחר כך בדקו את הסטטוס של ההרצה מדי כמה שניות:

curl https://app.nodaro.ai/v1/app/<slug>/runs/<runId> \
  -H "Authorization: Bearer $NODARO_API_TOKEN"
{
  "id": "run-uuid",
  "executionId": "exec-uuid",
  "status": "pending|running|completed|failed",
  "creditsUsed": 0,                          // the run's total, every attempt included
  "thumbnailUrl": null,                       // set when the run completes
  "execution": {
    "status": "pending|running|completed|failed",
    "nodeStates": {
      "node-abc": {
        "status": "completed",
        "output": {                           // the shape depends on the node's outputType
          "url": "https://.../result.png",
          "imageUrl": "...",
          "videoUrl": "...",
          "audioUrl": "...",
          "resultUrl": "...",
          "text": "..."
        }
      }
    },
    "totalNodes": 5,
    "completedNodes": 5,
    "failedNodes": 0,
    "totalCreditsUsed": 5,                    // this attempt's own
    "errorMessage": null,
    "completedAt": "2026-05-07T..."
  }
}
  • בדקו את הסטטוס בערך כל 2 שניות. הפסיקו כש-execution.status הוא completed או failed.
  • הציגו את ההתקדמות בתור completedNodes מתוך totalNodes בזמן שההרצה רצה.
  • כשההרצה נכשלת, הציגו את execution.errorMessage.

בעל ההרצה יכול לשנות רק את השדות name, inputValues, hiddenNodes ו-nodeStates שלה, עם PATCH /v1/app/{slug}/runs/{runId}. השרת מתעלם מכל שדה אחר, ובקשת PATCH שלא כוללת אף אחד מארבעת השדות האלה נדחית עם 400 bad_request.

התשובות שמחזירות הרצה כוללות את העריכות שלכם לתוצאות ההרצה ב-nodeStateEdits, המיפוי שבקשת ה-PATCH קובעת עם nodeStates. בקשת ה-PATCH מחליפה את כל המיפוי הזה. שלחו את כל העריכות שכבר שמורות, יחד עם העריכות החדשות שלכם, אחרת עריכות שתשמיטו יאבדו.

שלב 5: הצגת התוצאה

התוצאה הראשית היא הפלט של הרכיב ש-thumbnailNodeId מציין. קבלו את הכתובת שלה מ-execution.nodeStates[thumbnailNodeId].output, ונסו את המפתחות האלה לפי הסדר: url, imageUrl, videoUrl, audioUrl, resultUrl, ואחריהם text לתוצאת טקסט, שאותה מציגים כטקסט ולא כקישור.

הציגו אותה לפי ה-outputType של הרכיב, שמגיע מ-GET /v1/nodes/{type}:

outputTypeאיך להציג אותה
imageתמונה
videoנגן וידאו עם פקדים
audioנגן אודיו עם פקדים
textטקסט מעוצב מראש או Markdown
dataתצוגת JSON, או נתונים אטומים

כש-thumbnailNodeId הוא null, השתמשו ברכיב האחרון לפי הסדר של התהליך, או הציגו כל פלט שהוא לא ריק.

רינדור סופי של הרצה

כשרינדור של רכיב החלת רשימת עריכה באפליקציה מפיק תצוגה מקדימה, הדף של האפליקציה מציג עליה את הכפתור רינדור סופי. בקוד, שלחו POST /v1/app/{slug}/runs/{runId}/render-final עם { "renderNodeId": "<render node id>" }. השרת מרנדר את הרינדור הזה במצב סופי, להרצה הזו בלבד, ואז מריץ פעם אחת כל רכיב שאחריו. שום דבר שלפני הרינדור לא רץ שוב, ולא משלמים עליו שוב.

התשובה היא 202 עם { executionId, runId, status: "pending" }. כשיש כותרת Idempotency-Key באורך של 8 תווים או יותר, התגובה לניסיון חוזר של אותה בקשה היא 200 עם "deduped": true, ושום דבר לא מתחיל.

  • מי משלם. על הרינדור הסופי משלם מי שמריץ את האפליקציה, לפי המחירים של הרכיבים עצמם, כהרצה שנייה מחוץ להרצת האפליקציה. תוספת המחיר של היוצר על האפליקציה לא חלה. הרינדור הסופי לא מוסיף שורה להיסטוריית ההרצות, ולא נספר במגבלת ההרצות היומית של האפליקציה.
  • הבדיקה שלכם נלקחת בחשבון. בדיקה של רכיב תוכנית עריכה ששמורה בהרצה דרך בקשת ה-PATCH שלמעלה חלה על הרינדור הסופי.
  • איפה התוצאות מופיעות. כשיש לרינדור הסופי תוצאות, הנתיב GET /v1/app/{slug}/runs/{runId} מחזיר אותן ב-execution.nodeStates, במקום המצבים של התצוגה המקדימה, ומסמן כל אחת מהן ב-"fromRenderFinal": true. ההרצה כוללת גם finalExecution: { id, status, completedNodes, totalNodes, errorMessage, completedAt, creditsUsed }, או null כשלא התבקש רינדור סופי. ה-creditsUsed של ההרצה עצמה ממשיך לשקף רק את הרצת האפליקציה.
  • את רכיב הידוק פרק משלימים באותה דרך. רכיב הידוק פרק שהמתג סקירה לפני רינדור מופעל בו שומר תצוגה מקדימה משלו, ולכן ציינו את הרכיב עצמו בתור renderNodeId. הרינדור הסופי מרנדר את הפרק במצב סופי עם הבדיקה שלכם של העריכה, ואז מוסיף את הכתוביות ואת מוזיקת הרקע שלו, בלי לשלם שוב את המחיר של רכיב הידוק פרק. הרכיבים שאחריו כבר רצו על התצוגה המקדימה בהרצת האפליקציה, והם רצים שוב ברינדור הסופי. רק טייק של תצוגה מקדימה עובר רינדור סופי: טייק שכבר במצב סופי, או טייק שרץ פעם אחת לכל פריט ברשימה, נדחה עם render_final_not_preview.
  • רינדור סופי שנכשל לא מוצג. השדה finalExecution מדווח על ה-status ועל ה-errorMessage שלו, ותצוגות ההרצה חוזרות להציג את התצוגה המקדימה.
סטטוסקודמשמעות
400not_a_renderהשדה renderNodeId הוא לא רכיב רינדור של האפליקציה.
400preview_render_nestedהאפליקציה היא רכיב שמור.
402insufficient_app_creditsהקרדיטים שלכם לא מכסים את הרינדור הסופי.
404not_foundאין לכם הרצה כזו באפליקציה הזו.
409continuation_not_completedההרצה היא טיוטה שעוד לא רצה, או שהיא לא הושלמה.
409already_runningרינדור סופי של ההרצה הזו עדיין רץ. המזהה שלו נמצא ב-executionId.
503run_finals_unavailableכרגע אי אפשר לקרוא את הרינדורים הסופיים הקודמים של ההרצה. נסו שוב: שום דבר לא התחיל.

כל דחייה מגיעה לפני שמשהו נכתב ולפני כל חיוב.

ניסיון חוזר של הרצה שנכשלה

את הרצת האפליקציה שנכשלה ממשיכים מהמקום שבו היא נעצרה, ולא מתחילים אותה מההתחלה. שלחו POST /v1/app/{slug}/runs/{runId}/resume. זה מה שהכפתור נסו שוב בדף של האפליקציה עושה. רק השלבים שההרצה לא השלימה רצים שוב. כל שלב שהיא השלימה מעביר הלאה את התוצאה שלו, ולא משלמים עליו שוב. רכיב קליפ UGC שנכשל מרנדר רק את הקליפים שהוא לא סיים.

  1. שלחו קודם { "dryRun": true, "version": <n> }. התשובה היא 200 עם steps (השלבים שירוצו שוב), estimatedCredits (הסכום הצפוי לחיוב על הניסיון החוזר, כולל תוספת המחיר של היוצר), reusedClips, sufficient ו-available. השדה sufficient מציין אם היתרה שלכם והקרדיטים לאפליקציות שלכם מכסים את הניסיון החוזר. השדה appCreditsAllowance מופיע רק כשהקרדיטים לאפליקציות הם מה שעוצר אותו: הניסיון החוזר יידחה אז עם insufficient_app_credits. שום דבר לא מתחיל.
  2. הציגו למשתמש את השלבים ואת המחיר.
  3. אחרי שהמשתמש מסכים, שלחו את אותו גוף בקשה בלי dryRun. התשובה היא 202 עם { executionId, runId, status: "pending", nodeIds }. בדקו את סטטוס ההרצה מדי כמה שניות, כמו בשלב 4.
  • הגרסה והקלטים של ההרצה עצמה. הניסיון החוזר מריץ את גרסת האפליקציה שההרצה הריצה, עם הקלטים שההרצה השתמשה בהם. הוא לא מקבל קלטים משלו. שלחו את השדה "version" עם הגרסה שהממשק שלכם מריץ. בלי השדה הזה, הניסיון החוזר משתמש בגרסה האחרונה של האפליקציה.
  • התחלה מחדש במקום זאת. כדי להריץ עם קלטים אחרים או בגרסה הנוכחית של האפליקציה, התחילו את ההרצה מחדש: שלחו POST /v1/app/{slug}/run עם "runId": "<runId>". כל שלב רץ שוב, ומשלמים עליו שוב.
  • ניסיון חוזר אחד בכל פעם. שתי לחיצות בבת אחת מתחילות ניסיון חוזר אחד: התשובה השנייה היא 200 עם "deduped": true. לחיצה בזמן שהניסיון החוזר ממתין נדחית עם 409 already_running, והתגובה מציינת את המזהה שלו. ניסיון חוזר של ניסיון חוזר ממשיך ממה שהניסיון האחרון השלים.
  • תוספת המחיר של היוצר נגבית פעם אחת, על מה שהניסיון החוזר מריץ. כמו הרצת אפליקציה, הניסיון החוזר מוציא מהקרדיטים לאפליקציות של מי שמריץ אותו.
  • קרדיטים בכל הניסיונות. ה-creditsUsed של ההרצה הוא הסכום הכולל שלה בכל הניסיונות: מה שההרצה שנכשלה הוציאה ועוד מה שכל ניסיון חוזר הוציא. הוא מתעדכן בזמן שניסיון חוזר רץ. השדה execution.totalCreditsUsed הוא ההוצאה של הניסיון הנוכחי בלבד. התחלה מחדש של ההרצה פותחת סכום כולל חדש.
  • נתוני השימוש סופרים הרצה פעם אחת. בנתוני השימוש של האפליקציה, הרצה נספרת לפי התוצאה של הניסיון האחרון שלה שהסתיים. הרצה שנכשלה ואז הושלמה בניסיון חוזר היא הרצה מוצלחת אחת. ניסיון חוזר לא מוסיף הרצה. הקרדיטים של כל ניסיון נספרים ביום שבו הניסיון הזה הסתיים.
  • דרך ה-SDK, ה-CLI ו-MCP. ב-SDK הניסיון החוזר הוא client.apps.resume, ב-CLI הוא nodaro apps run-retry, ובשרת ה-MCP הוא retry_app_run.
סטטוסקודמשמעות
400validation_errorמזהה הרצה או גוף בקשה פגומים, או הרצה של רכיב שמור. את ההרצה של רכיב שמור ממשיכה ההרצה שקראה לו.
400continuation_workflow_mismatch / continuation_version_mismatchההרצה שמאחורי הרצת האפליקציה הריצה תהליך אחר, או גרסה אחרת שלו. זה נדיר. התחילו את ההרצה מחדש.
400locked_fieldקלט שההרצה השתמשה בו קובע שדה שהאפליקציה לא מאפשרת למי שמריץ לקבוע.
400preview_review_requiredהניסיון החוזר היה נעצר בתצוגה מקדימה, בלי הסימון שהדף של האפליקציה מוסיף.
402insufficient_app_credits / insufficient_creditsהקרדיטים לאפליקציות או היתרה של מי שמריץ לא מכסים את הניסיון החוזר.
404not_foundאין לכם הרצה כזו באפליקציה הזו.
404continuation_not_foundההרצה שמאחורי הרצת האפליקציה לא שייכת לכם.
409app_run_version_unavailableהגרסה שההרצה הריצה כבר לא זמינה. התחילו את ההרצה מחדש.
409app_run_version_changedהאפליקציה בגרסה אחרת מזו של ההרצה (appVersion, runVersion). התחילו את ההרצה מחדש, או שלחו את הגרסה של ההרצה.
409continuation_not_failedההרצה לא נכשלה: היא הושלמה, או שעצרו אותה. התחילו אותה מחדש.
409already_runningההרצה עדיין רצה (executionId), או שהתחילו אותה שוב במקום אחר.
409continuation_subset_requiredכל השלבים של ההרצה הושלמו. אין מה לנסות שוב. הסטטוס הוא 400 כשהאפליקציה לא מריצה שום שלב.
503billing_unavailableהחיוב לא זמין לרגע בהרצה של סביבת עבודה. נסו שוב בעוד רגע.

כשניסיון חוזר נדחה עם 409, הציעו להתחיל את ההרצה מחדש.

מחיקת הרצה

curl -X DELETE https://app.nodaro.ai/v1/app/<slug>/runs/<runId> \
  -H "Authorization: Bearer $NODARO_API_TOKEN"

בקשת מחיקה מעבירה את ההרצה לארכיון של המשתמש, והתגובה היא { "success": true, "archived": true }. שום דבר לא נמחק לצמיתות: אוטומציה שמוחקת את ההרצה הלא נכונה לא יכולה לגרום לאובדן הנתונים של המשתמש. אפשר לשחזר הרצה או למחוק אותה לצמיתות רק ב-Nodaro עצמו, ולכן האינטגרציה שלכם לא צריכה לנסות לעשות אף אחד מהשניים.

אימות

כל הרצה צריכה אסימון Bearer. בחרו את סוג האסימון לפי מי שאמור להיות הבעלים של ההרצות ולשלם עליהן:

מה אתם בוניםבמה להשתמש
כלי אישי, לוח בקרה של סוכנות או אוטומציה פנימיתאסימון API אישי.
SaaS שבו לקוחות מחברים את חשבונות ה-Nodaro שלהםOAuth.
כלי ציבורי שמריץ אפליקציה בשביל מבקרים אנונימייםאסימון API אישי. אתם משלמים, ולכן הגבילו בצד שלכם את מספר ההרצות לכל מבקר.

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

OAuth. החשבון של כל משתמש הוא הבעלים של ההרצות שלו ומשלם עליהן. רשמו יישום מפתחים, ושלחו את המשתמש למסך ההסכמה עם ההרשאות workflows:execute ו-jobs:read. ההרשאה workflows:execute מאפשרת להתחיל הרצות, לנסות אותן שוב, לבצע את הרינדור הסופי שלהן ולשנות את השם שלהן או למחוק אותן. ההרשאה jobs:read מאפשרת לבדוק את הסטטוס של הרצה ולקבל רשימת הרצות, וגם כל אחת מההרשאות workflows:execute או assets:read לבדה מספיקה לפעולות האלה. טעינת האפליקציה עם GET /v1/app/{slug} לא דורשת הרשאה. בשרת שלכם, החליפו את הקוד באסימון גישה שתקף ל-90 ימים. אם קריאה נדחית עם 403 insufficient_scope, שלחו את המשתמש שוב דרך מסך ההסכמה עם ההרשאות הרחבות יותר.

שמירת האסימון בשרת שלכם

אסור שהאסימון יגיע לדפדפן. ה-bundlers משלבים כל משתנה סביבה עם התחילית VITE_ או NEXT_PUBLIC_ בקוד ה-JavaScript שהם מפיקים, ושם כל מי שפותח את כלי המפתחים יכול לקרוא אותו ולהוציא את הקרדיטים שלכם.

[Browser]  --HTTPS-->  [Your server or edge function]  --HTTPS-->  Nodaro API
                         (holds NODARO_API_TOKEN)
סטאקאיפה האסימון נשמר
Lovable עם Supabaseסוד של Supabase Edge Function: supabase secrets set NODARO_API_TOKEN=...
Next.jsroute handler בצד השרת, עם process.env.NODARO_API_TOKEN ובלי התחילית NEXT_PUBLIC_
SvelteKit, Remix, Nuxtמשתנה סביבה של השרת בלבד, כגון $env/static/private ב-SvelteKit
פונקציית קצה ב-Vercel או ב-Netlifyמשתנה סביבה של הפרויקט שלא נחשף ללקוח
Cloudflare Workerסוד של ה-Worker: wrangler secret put NODARO_API_TOKEN

קוד הדפדפן שלכם מדבר רק עם השרת שלכם, ולכן אין צורך להוסיף את הדומיין שלכם למקורות המורשים של Nodaro. אם בכל זאת אתם קוראים ל-Nodaro מהדפדפן עם OAuth, רשמו את ה-origin שלכם בשדה מקורות מורשים של יישום המפתחים.

שגיאות

שגיאות מגיעות בצורה { "error": { "code": "...", "message": "..." } }.

סטטוסקודסיבהמה לעשות
400validation_errorהשדה inputOverrides פגום, ערך מחוץ ל-allowedValues או מחוץ לרשימה המותרת של בורר, או slug פגום.תקנו את גוף הבקשה.
400input_too_longקלט טקסט ארוך מה-maxLength של השדה.קצרו את הטקסט למגבלה שהפריט מצהיר עליה.
400locked_fieldדריסה מציינת יעד של רכיב ששולח החוצה, כגון הכתובת של רכיב פלט Webhook, או שדה שרכיב UGC לא מקבל מהרצה.הסירו אותה. האפליקציה קובעת לאן היא שולחת ומאיפה היא מביאה, ורכיב UGC מקבל רק את השדות של כרטיס היוצר ואת אורך הסרטון.
400ugc_photo_requiredרכיב יוצר/ת UGC מוגדר לאפשרות התמונה שלי, וההרצה לא נותנת תמונה של מי שמריץ אותה.בקשו את התמונה של מי שמריץ, או העבירו את הכרטיס לאפשרות יוצר/ת בינה מלאכותית.
401unauthorizedהאסימון חסר, פג תוקפו או בוטל.צרו אסימון חדש, או אשרו את הגישה שוב.
402insufficient_app_creditsאין בחשבון של האסימון מספיק קרדיטים.הוסיפו קרדיטים או שנו את התוכנית.
402insufficient_creditsאפליקציה שיוצרת סרטון UGC: היתרה לא מכסה את מה שעשוי להיות משוריין לסרטון בזמן שהקליפים שלו מרונדרים.הוסיפו קרדיטים, או צרו סרטון קצר יותר.
402insufficient_creditsאפליקציה עם תוספת מחיר של היוצר: היתרה לא מכסה את מחיר ההרצה המוצג של האפליקציה יחד עם התוספת. בהרצה בסביבת עבודה של אפליקציה שהארגון לא אישר, היתרה של מי שמריץ לא מכסה את התוספת. ההרצה של היוצר עצמו לא נבדקת.הוסיפו קרדיטים.
402budget_exceededהרצה בסביבת עבודה של אפליקציה עם תוספת מחיר של היוצר שהארגון אישר: התקציב של סביבת העבודה לא מכסה את מחיר ההרצה המוצג של האפליקציה יחד עם התוספת.בקשו ממנהל של סביבת העבודה להגדיל את היתרה הפנויה.
403insufficient_scopeלאסימון ה-OAuth חסרה הרשאה, שמופיעה ב-missingScope.אשרו את הגישה שוב עם ההרשאות הרחבות יותר.
404not_foundה-slug או ההרצה לא קיימים, האפליקציה הושבתה, או שההרצה שמאחורי הרצת האפליקציה לא שייכת לבעלים של ההרצה.בדקו את ה-slug, והציגו ״האפליקציה לא זמינה״.
429rate_limit_exceededהמשתמש הגיע למגבלת ההרצות היומית של האפליקציה.הציגו ״הגעתם למגבלה היומית״.
500internal_errorתקלה בשרת.נסו שוב פעם אחת אחרי הפסקה קצרה, ודווחו על התקלה אם היא חוזרת.

קוד לדוגמה

Supabase Edge Function כפרוקסי לאפליקציה

// supabase/functions/nodaro-run/index.ts
import { serve } from "https://deno.land/std@0.224.0/http/server.ts"

const BASE = Deno.env.get("NODARO_BASE_URL")!       // for example https://app.nodaro.ai
const TOKEN = Deno.env.get("NODARO_API_TOKEN")!     // ndr_...
const SLUG = Deno.env.get("NODARO_APP_SLUG")!       // my-cool-app

serve(async (req) => {
  const url = new URL(req.url)

  // Public probe: no token is sent, because the endpoint is public.
  if (req.method === "GET" && url.pathname.endsWith("/schema")) {
    const r = await fetch(`${BASE}/v1/app/${SLUG}`)
    return new Response(await r.text(), { status: r.status, headers: { "content-type": "application/json" } })
  }

  // Start a run.
  if (req.method === "POST" && url.pathname.endsWith("/run")) {
    const { inputs } = await req.json()
    const r = await fetch(`${BASE}/v1/app/${SLUG}/run`, {
      method: "POST",
      headers: {
        "Authorization": `Bearer ${TOKEN}`,
        "Content-Type": "application/json",
      },
      body: JSON.stringify({ inputOverrides: inputs }),
    })
    return new Response(await r.text(), { status: r.status, headers: { "content-type": "application/json" } })
  }

  // Poll a run.
  const m = url.pathname.match(/\/runs\/([0-9a-f-]{36})$/)
  if (req.method === "GET" && m) {
    const r = await fetch(`${BASE}/v1/app/${SLUG}/runs/${m[1]}`, {
      headers: { "Authorization": `Bearer ${TOKEN}` },
    })
    return new Response(await r.text(), { status: r.status, headers: { "content-type": "application/json" } })
  }

  return new Response("Not found", { status: 404 })
})

קוד לדפדפן: קריאה, הרצה ובדיקת סטטוס

// 1. On mount: read the schema and build the form (steps 1 to 3).
const schema = await fetch("/functions/v1/nodaro-run/schema").then((r) => r.json())
const inputItems = schema.snapshotSettings?.presentationSettings?.inputItems ?? []

// 2. On submit: send the values keyed by node id.
const start = await fetch("/functions/v1/nodaro-run/run", {
  method: "POST",
  headers: { "Content-Type": "application/json" },
  body: JSON.stringify({
    inputs: { "node-abc": { prompt: form.prompt, aspectRatio: form.aspectRatio } },
  }),
}).then((r) => r.json())

// 3. Poll every 2 seconds until the run ends.
let last = start
while (last.status !== "completed" && last.execution?.status !== "completed"
       && last.status !== "failed" && last.execution?.status !== "failed") {
  await new Promise((res) => setTimeout(res, 2000))
  last = await fetch(`/functions/v1/nodaro-run/runs/${start.runId}`).then((r) => r.json())
}

// 4. Read the result.
const out = last.execution?.nodeStates?.[schema.thumbnailNodeId]?.output ?? {}
const heroUrl = out.url ?? out.imageUrl ?? out.videoUrl ?? out.audioUrl ?? out.resultUrl
const heroText = out.text

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

רשימת בדיקות לפני יצירת קוד הממשק

  • הקריאה ל-GET {BASE}/v1/app/{slug} החזירה את מבנה הקלטים ואת ערכי ברירת המחדל.
  • לכל nodeId ב-inputItems, אתם יודעים מה ה-type שלו לפי snapshotNodes.
  • לכל סוג רכיב, הקריאה ל-GET {BASE}/v1/nodes/{type} נתנה לכם את הקטגוריה, את ה-outputType ואת המודלים שלו.
  • בניתם את רשימת השדות של הטופס לפי הכללים של שלב 2.
  • אתם יודעים מהו רכיב התוצאה, thumbnailNodeId או הרכיב האחרון, ומה ה-outputType שלו.
  • יש לכם אסימון API אישי או פרטי גישה של יישום מפתחים.
  • הסוד נשמר בשרת שלכם.

שאלות נפוצות

איך מטמיעים מיני־אפליקציה של Nodaro באתר שלי?

פתחו את הדף מיני־אפליקציות, בחרו בלשונית המיני־אפליקציות שלי ולחצו על הכפתור הטמעה בכרטיס של האפליקציה. הוסיפו את הדומיין של האתר שלכם תחת דומיינים מורשים להטמעה, לחצו על הכפתור העתקת קוד הטמעה והדביקו את ה-iframe בדף שלכם. ההטמעה חסומה עד שמוסיפים לפחות דומיין אחד.

באילו נקודות קצה צריך להשתמש כדי לבנות ממשק משלי למיני־אפליקציה?

כדי לגלות מהם הקלטים של מיני־אפליקציה, קוראים לשתי נקודות קצה ציבוריות: GET /v1/app/{slug}‎ ו-GET /v1/nodes/{type}‎. אחר כך מתחילים הרצה עם POST /v1/app/{slug}/run ובודקים את הסטטוס שלה מדי כמה שניות עם GET /v1/app/{slug}/runs/{runId}‎. שתי נקודות הקצה האלה דורשות אסימון.

האם Nodaro קורא לשרת שלי כשהרצה של מיני־אפליקציה מסתיימת?

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

מי משלם על ההרצות של מיני־אפליקציה מוטמעת?

על ההרצות של מיני־אפליקציה מוטמעת משלם החשבון שההרצה פועלת בשמו. עם אסימון API אישי, החשבון שלכם משלם על כל הרצה. עם OAuth, החשבון של כל משתמש משלם על ההרצות שלו.

האם אפשר למחוק הרצה של מיני־אפליקציה דרך ה-API?

כן. בקשת DELETE להרצה של מיני־אפליקציה מעבירה אותה לארכיון, ואפשר לשחזר אותה ב-Nodaro. אפשר לשחזר הרצה או למחוק אותה לצמיתות רק ב-Nodaro עצמו.

עודכן לאחרונה

בדף הזה