NodaroAccount
Nodaro Docs
DocumentationNode ReferenceModelsTemplatesAI Agents (MCP)DevelopersSelf-hostingResearchCompare
REST API

Pipelines

Start a Story to Video pipeline over REST, follow its stages, approve gates, chat with the director, and branch a finished pipeline from a stage.

Available on Nodaro Cloud

The Pipelines API runs Story → Video from code. You send a one-line story. The pipeline engine writes a script, creates the cast, props and places, and plans the shots. It then renders keyframes, animates them with sound and merges the film. Each stage can stop for your approval, run by itself, or open a chat with the director.

Pipelines run on Nodaro Cloud only; Community and Business editions answer 403 edition_required. On a self-hosted install, build the same steps as a workflow with nodes such as Generate Script, Generate Image and Generate Video. The routes take a bearer token; OAuth app tokens need the scopes listed below. See Authentication.

Endpoints

MethodPathScopeWhat it does
POST/v1/pipelinespipelines:executeCreate and start a pipeline.
GET/v1/pipelinespipelines:readList your pipelines, most recent first.
GET/v1/pipelines/:idpipelines:readGet the status, the current stage and the credits.
GET/v1/pipelines/:id/eventspipelines:readStream the pipeline's events (server-sent events).
GET/v1/pipelines/:id/stages/:stagepipelines:readGet one stage's status, output and reviewer feedback.
GET/v1/pipelines/:id/pending-approvalspipelines:readList the stages waiting for approval.
GET/v1/pipelines/:id/timelinepipelines:readGet the assembled film: scenes, durations and audio.
GET/v1/pipelines/:id/entities?type=pipelines:readList the pipeline's characters, objects, locations or scenes.
POST/v1/pipelines/:id/stages/:stage/approvepipelines:approveApprove a stage, optionally with edits.
POST/v1/pipelines/:id/stages/:stage/rejectpipelines:approveReject the script with feedback, so it is written again.
POST/v1/pipelines/:id/sub-gates/:gate/approvepipelines:approveApprove a checkpoint inside the animate stage.
POST/v1/pipelines/:id/sub-gates/:gate/rejectpipelines:approveReject that checkpoint and stop the pipeline.
POST/v1/pipelines/:id/entities/:sceneId/helpers/accept_match_cut_breakpipelines:approveAccept one match-cut break in the scene images stage.
POST/v1/pipelines/:id/stages/:stage/chatpipelines:approveSend a message to the director (guided mode).
GET/v1/pipelines/:id/stages/:stage/chatpipelines:readRead the chat of a stage.
POST/v1/pipelines/:id/stages/:stage/chat/turns/:turnId/applypipelines:approveApply a change the director proposed.
POST/v1/pipelines/:id/branchpipelines:executeRe-run a completed pipeline from a stage, as a new pipeline.
POST/v1/pipelines/:id/forkpipelines:executeStop the pipeline and keep its canvas as ordinary nodes.
POST/v1/pipelines/:id/cancelpipelines:executeCancel a running pipeline and refund unspent credits.

Stages and modes

A pipeline walks eight stages in order. :stage in a path is one of these names.

StageWhat it produces
scriptThe story plan: the title, the scenes, the cast, the locations and the props.
charactersA character for each role in the cast.
objectsThe props the story needs.
locationsThe places the story visits.
shot_listThe shots of each scene, with their camera and continuity choices.
scene_imagesA keyframe for each shot.
animate_audio_editThe animated shots, with dialogue, narration, music and the edit.
post_mergeThe final merged film.

The mode you choose at creation decides who moves the pipeline forward:

ModeWhat happens
manual (default)Every stage stops at awaiting_approval. You approve it, edit it or reject it, and the pipeline goes on.
autoThe engine runs every stage by itself. Automatic reviewers check the script, the coverage of the cast, locations and props, and the keyframes. After 3 blocking verdicts in a row, the pipeline fails and the unspent credits are refunded. It stops only at a match-cut break, until every break is accepted. Meanwhile the status stays running, and pending-approvals lists the scene_images stage.
guidedLike manual, plus a chat with the director on the script and post_merge stages.

Start a pipeline

POST /v1/pipelines creates the pipeline, reserves its credits and starts it. It answers 201 with { id }.

curl -X POST https://app.nodaro.ai/v1/pipelines \
  -H "Authorization: Bearer $NODARO_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "root_node_id": "0e6b2f7c-4a1d-4c8e-9b3f-5d7a2c1e8f4b",
    "story_prompt": "A lighthouse keeper must restart the light before the storm hits.",
    "format": "short_film",
    "target_duration_seconds": 60,
    "mode": "auto",
    "output_resolution": "720p"
  }'
import { createClient, StaticTokenAuth } from '@nodaro/sdk'

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

const { id } = await client.pipelines.create({
  root_node_id: crypto.randomUUID(),
  story_prompt: 'A lighthouse keeper must restart the light before the storm hits.',
  format: 'short_film',
  target_duration_seconds: 60,
  mode: 'auto',
})
{ "id": "c2f8a4e6-1b9d-4f3a-8c7e-6d2b9f1a5e3c" }

Prop

Type

Each format allows a range of lengths:

FormatShortestLongest
reel7 s90 s
commercial10 s90 s
trailer30 s180 s
short_film12 s600 s
music_video30 s600 s

Follow a pipeline

GET /v1/pipelines/:id returns the pipeline's state. Poll it every few seconds, or open GET /v1/pipelines/:id/events to receive the changes as server-sent events.

{
  "id": "c2f8a4e6-1b9d-4f3a-8c7e-6d2b9f1a5e3c",
  "status": "running",
  "current_stage": "scene_images",
  "mode": "auto",
  "spent_credits": 412,
  "reserved_credits": 1180,
  "upfront_credit_estimate": 1650,
  "failure_reason": null,
  "current_progress_message": "Rendering keyframe 5 of 9",
  "branched_from_pipeline_id": null,
  "branched_from_stage": null
}

status is queued, running, awaiting_approval, completed, failed, cancelled or forked. failure_reason explains a failed pipeline.

When the pipeline is completed, GET /v1/pipelines/:id/timeline returns the film as data you can render or hand to an editor:

{
  "fps": 24,
  "width": 1280,
  "height": 720,
  "scenes": [
    { "compositeUrl": "https://cdn.nodaro.ai/pipelines/scene-1.mp4", "durationSeconds": 8.5 },
    { "compositeUrl": "https://cdn.nodaro.ai/pipelines/scene-2.mp4", "durationSeconds": 11 }
  ],
  "musicUrl": "https://cdn.nodaro.ai/pipelines/score.mp3",
  "narrationUrl": "https://cdn.nodaro.ai/pipelines/narration.mp3"
}

While the animate stage runs, the timeline also carries animateProgress: { totalShots, shotsDone, percent }. To finish the cut in an external editor, see Export a timeline.

curl https://app.nodaro.ai/v1/pipelines/c2f8a4e6-1b9d-4f3a-8c7e-6d2b9f1a5e3c \
  -H "Authorization: Bearer $NODARO_API_KEY"

curl https://app.nodaro.ai/v1/pipelines/c2f8a4e6-1b9d-4f3a-8c7e-6d2b9f1a5e3c/timeline \
  -H "Authorization: Bearer $NODARO_API_KEY"
const pipeline = await client.pipelines.get(id)
console.log(pipeline.status, pipeline.current_stage)

const timeline = await client.pipelines.getTimeline(id)
for (const scene of timeline.scenes) {
  console.log(scene.compositeUrl, scene.durationSeconds)
}

Approve or reject a stage

In manual and guided mode, each stage stops at awaiting_approval. GET /v1/pipelines/:id/pending-approvals lists the stages waiting, each { stage_name, output }. Read a stage in full with GET /v1/pipelines/:id/stages/:stage, which returns { status, output, critic_feedback }.

  • Approve. POST /v1/pipelines/:id/stages/:stage/approve returns { ok: true } and the pipeline moves on. To change the stage's output first, send { edits }: a JSON Patch applied to the output before approval.
  • Reject. Only the script can be rejected. POST /v1/pipelines/:id/stages/script/reject with { feedback } returns { ok: true }, and the engine writes the script again with your note in mind. Another stage answers 400 stage_not_implemented. You can reject the script at most twice, and fewer times if the automatic script reviewer has already revised it.
curl -X POST https://app.nodaro.ai/v1/pipelines/c2f8a4e6-1b9d-4f3a-8c7e-6d2b9f1a5e3c/stages/script/approve \
  -H "Authorization: Bearer $NODARO_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "edits": [{ "op": "replace", "path": "/title", "value": "The Last Light" }] }'

curl -X POST https://app.nodaro.ai/v1/pipelines/c2f8a4e6-1b9d-4f3a-8c7e-6d2b9f1a5e3c/stages/script/reject \
  -H "Authorization: Bearer $NODARO_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "feedback": "Make the story darker and more suspenseful." }'
const approvals = await client.pipelines.pendingApprovals(id)
const { output } = await client.pipelines.getStage(id, 'script')

await client.pipelines.approveStage(id, 'script', [
  { op: 'replace', path: '/title', value: 'The Last Light' },
])
// or
await client.pipelines.rejectStage(id, 'script', 'Make the story darker and more suspenseful.')

Checkpoints inside the animate stage

In manual and guided mode, the animate_audio_edit stage can stop at two checkpoints of its own:

  • dialogue_recheck. The stage compares the real length of the dialogue with the plan and adjusts the scene timing. When a scene cannot stay within 10% of its target, the stage waits for your approval.
  • silent_cut_preview. The stage assembles a preview of the cut without music and waits for your approval before it generates, and pays for, the music.

POST /v1/pipelines/:id/sub-gates/:gate/approve resumes the stage and returns { ok: true, gate, resumed_at }. POST /v1/pipelines/:id/sub-gates/:gate/reject fails the stage and the pipeline, and refunds the unspent credits. In auto mode, the stage continues without stopping.

Match-cut breaks in the scene images stage

The scene_images stage can stop at a third checkpoint, match_cut_break_pending. It stops there in every mode, auto included, when a planned match cut between two shots does not hold. The stage output lists the shots in match_cut_break_pending.

The sub-gate routes above do not clear it. They answer 400 invalid_sub_gate, and the error message names the route that does. Accept each break with POST /v1/pipelines/:id/entities/:sceneId/helpers/accept_match_cut_break and { "shotId": "<shot id>" }. sceneId is the id of the scene that holds the shot, not its metadata.scene_id. GET /v1/pipelines/:id/entities?type=scene lists the scenes, each with its shots in metadata.scene_node_data.shots.

Accepting a break needs the pipelines:approve scope and costs no credits. The call returns { ok: true, pendingRemaining }, the number of breaks still waiting. When the last one is accepted, the stage continues. An accepted break stays accepted when the scene_images stage runs again. Running the shot_list stage again plans the scenes anew and clears every acceptance, so a break that comes back must be accepted again.

Chat with the director

In guided mode, a stage waiting for approval has a chat. Send a message of up to 8,000 characters with POST /v1/pipelines/:id/stages/:stage/chat and { message }. The director answers in one sentence and may propose a change.

{
  "turnId": "f1a3c5e7-9b2d-4f6a-8c1e-3d5b7f9a2c4e",
  "role": "assistant",
  "content": "I moved the keeper's reason for staying into scene 2 and tightened the ending.",
  "proposed_change": {
    "change_type": "edit_artifact",
    "json_patch": [{ "op": "replace", "path": "/scenes/1/description", "value": "The keeper finds his late wife's log and decides to stay." }]
  }
}

proposed_change is null, an edit_artifact with a json_patch, or a suggest_branch with from_stage and a reason.

StageWhat the director can proposeTurns
scriptAn edit to the plan (a JSON Patch on the title, the scenes, the cast, the locations or the props), or a branch when the change is too structural to patch.20 per pipeline
post_mergeA diagnosis of the final film and a branch from an earlier stage. The film itself cannot be patched.8 per pipeline

To accept a proposal, call POST /v1/pipelines/:id/stages/:stage/chat/turns/:turnId/apply. It returns { applied: true, attemptId, newOutput } and approves the stage. When the change breaks the plan, for example by removing a character a scene still uses, it returns { applied: false, error } and the director adds a turn that explains what to fix. A proposal that is not valid, or a stage that is no longer waiting, answers 409. GET /v1/pipelines/:id/stages/:stage/chat returns { turns }, the whole conversation.

A guided pipeline reserves extra credits for the chat when it starts. Unused credits are refunded.

curl -X POST https://app.nodaro.ai/v1/pipelines/c2f8a4e6-1b9d-4f3a-8c7e-6d2b9f1a5e3c/stages/script/chat \
  -H "Authorization: Bearer $NODARO_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "message": "Make the keeper'\''s motivation clearer in scene 2." }'
const reply = await client.pipelines.chatStage(id, 'script', "Make the keeper's motivation clearer in scene 2.")
if (reply.proposed_change) {
  const result = await client.pipelines.applyChatProposal(id, 'script', reply.turnId)
  if (!result.applied) console.log(result.error.code)
}

Branch a finished pipeline

POST /v1/pipelines/:id/branch re-runs a completed pipeline from one stage, as a new pipeline. The stages before fromStage are copied as approved, fromStage starts running, and the stages after it are created fresh. The original pipeline stays completed.

The branch copies the characters, objects and locations into the new pipeline but reuses their image files, so no file is duplicated. It starts with an empty chat.

curl -X POST https://app.nodaro.ai/v1/pipelines/c2f8a4e6-1b9d-4f3a-8c7e-6d2b9f1a5e3c/branch \
  -H "Authorization: Bearer $NODARO_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "fromStage": "scene_images" }'
const branch = await client.pipelines.branch(id, { fromStage: 'scene_images' })
console.log(branch.pipelineId, branch.clonedStages)

The route answers 201 with { pipelineId, clonedStages, clonedEntities }: the new pipeline's id, the stages copied as approved, and the number of characters, objects and locations copied. fromStage is any stage name from the table above.

Cancel or fork a pipeline

  • Cancel. POST /v1/pipelines/:id/cancel stops a running pipeline, refunds the credits reserved for work that has not run, and returns { ok: true }. A pipeline that has already completed, failed or been cancelled answers 409 already_terminal, and nothing changes.
  • Fork. POST /v1/pipelines/:id/fork takes the canvas away from the pipeline. Every node it created becomes an ordinary node you can edit, the unspent credits are refunded, and the status becomes forked. A fork cannot be undone. To continue with the engine, start a new pipeline.

Availability

A deployment can offer or withhold the film pipeline like a node. Its availability id is film-pipeline, listed in the surface profile's nodes.allow or nodes.deny. See Editions and surface profiles.

Where the pipeline is not offered to you, these routes answer 403 node_not_available, before anything is reserved or charged:

  • POST /v1/pipelines.
  • Every POST under /v1/pipelines/:id/ that starts or continues work. That is branch, fork, stage approve, edit, reject and chat, applying a chat proposal, scene and shot regenerations, retries, and the entity, sub-gate and scene-helper actions.

The reads (GET), switching a run to manual (PATCH /v1/pipelines/:id) and POST /v1/pipelines/:id/cancel stay open. So you can still follow and stop a pipeline that you started before.

A pipeline that is already running when the feature is withheld does not start its next stage, also in auto mode. It ends failed with failure_reason: "node_not_available", and its unspent reservation is refunded. A pipeline paused at an approval gate stays paused.

Credits

The pipeline reserves its estimated cost when it starts; upfront_credit_estimate shows it. spent_credits and reserved_credits show where the run stands. A cancelled or failed pipeline refunds what it did not spend, and max_cost_credits caps the total. See Credits.

Use it from MCP and the SDK

The SDK wraps every route as client.pipelines.*. The CLI has no pipeline commands. AI assistants use these MCP tools:

ToolScopeWhat it does
start_pipelinepipelines:executeStart a pipeline. Its default mode is auto.
get_pipeline_statuspipelines:readRead the status, the stage and the credits.
pipeline_pending_approvalspipelines:readList the stages waiting for approval.
chat_pipeline_stage, apply_chat_proposalpipelines:approveChat with the director and apply a proposal.
get_pipeline_stage_chatpipelines:readRead a stage's chat.
branch_pipelinepipelines:executeBranch a completed pipeline.

See Film Director for the guided way to direct a film from an assistant.

Errors

StatusCodeMeaning
400validation_errorThe body is invalid, for example a length under 5 or over 3,600 seconds, or a branch fromStage that is not a stage.
400duration_out_of_boundsThe length is outside the format's range, or over 600 seconds.
400pipeline_not_completedA branch was requested from a pipeline that is not completed.
400invalid_stageSending or reading a chat names a stage other than script, shot_list or post_merge.
400invalid_stage_nameAn approval names a stage that is not one of the eight. Reading such a stage answers 404 with this code.
400stage_not_implementedA reject call names a stage other than script.
400invalid_change_type_for_stageA patch was proposed for the post_merge stage, which accepts only a branch.
400invalid_sub_gateThe gate is not one the sub-gate routes resolve. For match_cut_break_pending, the message names the route that accepts each break.
400shot_not_foundThe shot to accept is not in that scene.
400not_a_match_cutThe shot to accept has no planned match cut.
400invalid_entity_typeThe entities list was asked for a type other than character, object, location or scene.
401unauthorizedThe token is missing, invalid or revoked.
402insufficient_creditsThe account cannot cover the reservation.
403edition_requiredThe instance is not Nodaro Cloud.
403node_not_availableThis deployment does not offer the film pipeline (film-pipeline) to you. See Availability.
403insufficient_scopeAn OAuth app token lacks the route's scope.
404not_foundNo pipeline with that id belongs to you. On a sub-gate call, it also means the stage is not waiting at that gate.
404pipeline_not_foundA branch call names a pipeline that does not exist or is not yours.
404stage_not_startedThe stage you read has not started yet.
404stage_not_foundA sub-gate call reached a pipeline whose animate stage has not started. A stage approval or rejection answers 409 with this code.
404scene_not_foundThe pipeline has no scene with that id.
409patch_invalidA chat proposal is not a valid change and could not be applied.
409stage_not_awaitingThe stage is no longer waiting for approval, when you apply a chat proposal or approve with edits.
409stage_already_advancedThe stage is no longer waiting for approval, when you approve without edits or reject.
409stage_not_awaiting_approvalA sub-gate call reached an animate stage that is not waiting for approval.
409already_terminalA cancel call reached a pipeline that has already completed, failed or been cancelled.
409critic_retry_cap_reachedThe script has already been revised twice, by you or by the automatic script reviewer.
409scene_not_plannedThe scene has no shot plan yet, so no break can be accepted in it.
501chat_not_wired_for_stageThe stage has no working chat. Today that is only shot_list.

Frequently asked questions

What does a Story to Video pipeline produce?

A finished film and the workflow behind it. The pipeline writes a script, creates the characters, objects and locations, plans the shots, renders keyframes, animates them with sound, and merges the result. GET /v1/pipelines/:id/timeline returns the assembled scenes.

Can a pipeline run without anyone approving it?

Yes. Create it with mode set to auto and the engine runs every stage by itself, with automatic reviewers checking the output. Poll GET /v1/pipelines/:id until the status is completed or failed. The run stops only when a planned match cut between two shots breaks. The status then stays running, and GET /v1/pipelines/:id/pending-approvals lists the scene_images stage until every break is accepted.

How do I re-run a finished film from one stage?

Call POST /v1/pipelines/:id/branch with the stage to re-run from. Nodaro creates a new pipeline that keeps the stages before it and runs that stage and the ones after it again. The original stays unchanged.

What happens to my credits when I cancel a pipeline?

The credits reserved for work that has not run yet are refunded. Credits for work already done are kept. A pipeline that has already completed, failed or been cancelled cannot be cancelled. The call answers 409 already_terminal and changes nothing.

Can I run pipelines on a self-hosted install?

No. Pipelines run on Nodaro Cloud only, and self-hosted editions answer 403 edition_required. On a self-hosted install, build the same steps as a workflow on the canvas.

Last updated on

On this page