Pipelines
Start a story-to-video pipeline from TypeScript, follow it stage by stage, approve stages, chat with the director, and read the finished timeline.
client.pipelines runs story-to-video pipelines: multi-stage productions that turn a story prompt into a film. A pipeline moves through eight stages, script, characters, objects, locations, shot_list, scene_images, animate_audio_edit and post_merge, and it can stop at each one for your approval. It is the programmatic version of Create film in the studio. The methods call the Pipelines REST API.
Modes
| Mode | What happens at each stage |
|---|---|
manual (the default) | Every stage waits for your approval. Approve each stage; the script can also be rejected. |
auto | The engine approves every stage itself and runs to the end. It stops only at a match-cut break, until every break is accepted. Meanwhile the status stays running, and pendingApprovals(id) lists the scene_images stage. |
guided | Like manual, and you can also chat with the director about a stage and apply its proposed changes. |
A pipeline's status is queued, running, awaiting_approval, completed, failed, cancelled or forked.
Methods
| Method | Scope | What it does |
|---|---|---|
create(input) | pipelines:execute | Start a pipeline |
get(id) | pipelines:read | Read a pipeline's status and credits |
list() | pipelines:read | List your pipelines |
cancel(id) | pipelines:execute | Stop a pipeline |
pendingApprovals(id) | pipelines:read | List the stages waiting for approval |
approveStage(id, stage, edits?) | pipelines:approve | Approve a stage, with optional edits |
rejectStage(id, stage, feedback) | pipelines:approve | Reject the script and write it again |
approveSubGate(id, gate) | pipelines:approve | Approve a check inside the animation stage |
getStage(id, stage) | pipelines:read | Read one stage's output |
getTimeline(id) | pipelines:read | Read the assembled film |
branch(id, input) | pipelines:execute | Run a finished pipeline again from a stage |
chatStage(pipelineId, stage, message) | pipelines:approve | Ask the director to change a stage |
applyChatProposal(pipelineId, stage, turnId) | pipelines:approve | Accept a change the director proposed |
getStageChat(pipelineId, stage) | pipelines:read | Read a stage's chat |
The scopes apply to OAuth tokens. API tokens and sessions are not limited by scopes.
client.pipelines
create(input)
Starts a pipeline. In auto mode it runs to the end on its own, unless a match-cut break stops it; poll get() for its status and getTimeline() for the result. In manual or guided mode, drive it with pendingApprovals(), approveStage() and approveSubGate().
create(input: PipelineInput): Promise<{ id: string }>Prop
Type
const { id } = await client.pipelines.create({
story_prompt: "A lighthouse keeper finds a message in a bottle that predicts tomorrow's storm.",
target_duration_seconds: 60,
format: "short_film",
root_node_id: "pipeline-root",
mode: "auto",
})get(id)
Reads a pipeline's current state. Poll it to follow an auto run to the end. A run stopped at a match-cut break stays running, so check pendingApprovals(id) as well.
get(id: string): Promise<PipelineRecord>Prop
Type
const pipeline = await client.pipelines.get(id)
console.log(pipeline.status, pipeline.current_stage, pipeline.current_progress_message)A PipelineRecord has id, status, current_stage, mode, spent_credits, reserved_credits, upfront_credit_estimate, failure_reason (set when the status is failed), current_progress_message, and, for a branch, branched_from_pipeline_id and branched_from_stage.
list()
Lists your pipelines, newest first.
list(): Promise<PipelineRecord[]>const pipelines = await client.pipelines.list()cancel(id)
Stops a running pipeline. Unspent reserved credits are refunded. Cancelling a pipeline that has already ended changes nothing.
cancel(id: string): Promise<{ ok: true }>Prop
Type
await client.pipelines.cancel(id)pendingApprovals(id)
Lists the stages waiting for approval, each with its output. The list is empty during an auto run, because the engine approves stages itself, unless a match-cut break stops it.
pendingApprovals(id: string): Promise<Array<{ stage_name: PipelineStageName; output: unknown }>>Prop
Type
const approvals = await client.pipelines.pendingApprovals(id)
for (const { stage_name, output } of approvals) console.log(stage_name, output)approveStage(id, stage, edits?)
Approves a stage so the pipeline moves on. Pass edits, a JSON Patch, to change the stage's output before it is approved.
approveStage(id: string, stage: PipelineStageName, edits?: unknown): Promise<{ ok: true }>Prop
Type
await client.pipelines.approveStage(id, "script")
await client.pipelines.approveStage(id, "script", [
{ op: "replace", path: "/title", value: "The Keeper's Warning" },
])rejectStage(id, stage, feedback)
Rejects the script with a note, and the engine writes it again with the note in mind. Only the script stage can be rejected; another stage fails with stage_not_implemented. You can reject the script at most twice, and fewer times if the automatic script reviewer has already revised it.
rejectStage(id: string, stage: PipelineStageName, feedback: string): Promise<{ ok: true }>Prop
Type
await client.pipelines.rejectStage(id, "script", "Make the story darker and more suspenseful")approveSubGate(id, gate)
Approves a check inside the animate_audio_edit stage, such as dialogue_recheck, so the pipeline continues with the next step.
The scene_images stage has its own checkpoint, match_cut_break_pending, which approveSubGate does not clear. Accept each break through the REST API instead. See Match-cut breaks in the scene images stage.
approveSubGate(id: string, gate: SubGateName): Promise<{ ok: true; gate: SubGateName; resumed_at: string }>Prop
Type
await client.pipelines.approveSubGate(id, "dialogue_recheck")getStage(id, stage)
Reads one stage's status, output and critic_feedback. Use it to inspect the script or the plan before you approve it.
getStage(id: string, stage: PipelineStageName): Promise<{ status: string; output: unknown; critic_feedback: unknown }>Prop
Type
const { status, output } = await client.pipelines.getStage(id, "script")getTimeline(id)
Reads the assembled film: the scenes in order with their durations, the audio URLs and the animation progress. Render it yourself, or hand it to an editor.
getTimeline(id: string): Promise<PipelineTimeline>Prop
Type
const timeline = await client.pipelines.getTimeline(id)
for (const scene of timeline.scenes) console.log(scene.compositeUrl, scene.durationSeconds)A PipelineTimeline has fps, width, height, scenes (each { compositeUrl, durationSeconds }), musicUrl, narrationUrl, and animateProgress with totalShots, shotsDone and percent.
branch(id, input)
Runs a finished pipeline again from one stage, as a new pipeline. The stages before it are copied as approved. The original pipeline stays completed.
branch(id: string, input: { fromStage: PipelineStageName }): Promise<{ pipelineId: string; clonedStages: string[]; clonedEntities: number }>Prop
Type
const { pipelineId } = await client.pipelines.branch(id, { fromStage: "scene_images" })chatStage(pipelineId, stage, message)
Sends a message to the director in guided mode. The pipeline's mode must be guided, and the stage must be awaiting_approval. The reply may carry a proposed_change you can accept with applyChatProposal(). Chat works on the stages that support it, such as script.
chatStage(pipelineId: string, stage: ChatEnabledStage, message: string): Promise<{
turnId: string
role: "assistant"
content: string
proposed_change: ProposedChange | null
}>Prop
Type
const { content, proposed_change, turnId } = await client.pipelines.chatStage(
id,
"script",
"Can you make the protagonist's motivation clearer in scene 2?",
)applyChatProposal(pipelineId, stage, turnId)
Accepts the change the director proposed in an earlier turn. The change is checked, saved as a new version of the stage, and the stage is approved.
applyChatProposal(pipelineId: string, stage: ChatEnabledStage, turnId: string): Promise<
| { applied: true; attemptId: string; newOutput: unknown }
| { applied: false; error: { code: string; detail?: unknown } }
>Prop
Type
const result = await client.pipelines.applyChatProposal(id, "script", turnId)
if (result.applied) console.log("Approved:", result.newOutput)
else console.log("Not applied:", result.error.code)When the change cannot be applied but you can recover, the result is applied: false and the director has already added a reply with a hint. A hard failure throws a 409 error.
getStageChat(pipelineId, stage)
Reads a stage's chat history. It is empty before the first message.
getStageChat(pipelineId: string, stage: ChatEnabledStage): Promise<{ turns: ChatTurn[] }>Prop
Type
const { turns } = await client.pipelines.getStageChat(id, "script")Each ChatTurn has id, turn_n, role, content, proposed_change, applied_to_attempt_id and created_at.
Frequently asked questions
What is a Nodaro pipeline?
A pipeline makes a film from a story prompt in stages: script, characters, objects, locations, shot list, scene images, animation with audio and edit, and the final merge. It is the headless version of Create film in the studio.
How do I run a pipeline without approving each stage?
Create it with mode auto. The engine approves each stage itself and runs to the end. Poll client.pipelines.get(id) for the status and read the result with getTimeline(id). The run stops only when a planned match cut breaks. The status then stays running, and pendingApprovals(id) lists the scene_images stage until every break is accepted.
How do I change a stage's output before approving it?
Pass a JSON Patch as the third argument of approveStage. It is applied to the stage output before the stage is approved.
Which OAuth scopes do pipelines need?
pipelines:read to read pipelines and stages, pipelines:execute to create, cancel and branch them, and pipelines:approve to approve stages, reject the script and chat about stages.
Related
Pipelines
Film Director
Jobs and executions
Studio productions
Last updated on
Recast
Quote, buy and follow Recast runs from TypeScript, answer their picks, import an authored script, and change the music mix of a finished recast.
Copilot
client.copilot drives the Copilot assistant's threads and streamed turns. It works only inside a Nodaro app, with a signed-in user's own session.