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.
Available on Nodaro Cloud
client.recast runs Recast: it regenerates an analyzed source video with your own cast, and it can also render a script you wrote, a "movie as JSON". You quote a run, buy it, follow it, and answer its picks for the cast, the sheets, the anchor frames and the music. The methods call the Recast REST API. See Recast over MCP for the authoring guide.
Recast runs on Nodaro Cloud. On a self-hosted install, these methods throw NotFoundError.
Methods
| Method | What it does |
|---|---|
authoringSkill() | Read the script authoring guide |
validateScript(script) | Check an authored script |
importScript(script, opts) | Store an authored script as an analysis |
estimate(input) | Quote a run, for free |
create(input) | Buy the plan and create the run |
get(recastId) | Read the run's status and pending pick |
start(recastId, opts?) | Start rendering a planned run |
resolveGate(recastId, input) | Answer a pending pick |
estimateRescore(recastId, input) | Quote a new music mix or music track |
rescore(recastId, input) | Apply the quoted audio change |
Authored scripts
authoringSkill()
Returns the authoring guide, as Markdown (GET /v1/video-analysis/authoring-skill). It covers the script document, its vocabularies and limits, the audio rules, and a checked example. It is free.
authoringSkill(): Promise<string>const guide = await client.recast.authoringSkill()Give the guide to the language model that writes your script.
validateScript(script)
Checks an authored script and returns { valid, errors, warnings } (POST /v1/video-analysis/import/validate). It is free and stores nothing.
validateScript(script: Record<string, unknown>): Promise<{
valid: boolean
errors: Array<{ path: string; message: string; hint?: string }>
warnings: string[]
}>Prop
Type
let check = await client.recast.validateScript(script)
while (!check.valid) {
script = await fixWithModel(script, check.errors) // each error has a path, a message and usually a hint
check = await client.recast.validateScript(script)
}Each error names the path that is wrong and usually carries a hint written for a language model. Fix and validate again until valid is true.
importScript(script, opts)
Stores a valid script as a completed analysis job (POST /v1/video-analysis/import). Use its jobId as the analysisJobId of a recast. It is free.
importScript(script: Record<string, unknown>, opts: { rightsAttested: true }): Promise<{
jobId: string
created: boolean
warnings: string[]
json: Record<string, unknown>
}>Prop
Type
const { jobId: analysisJobId, json } = await client.recast.importScript(script, { rightsAttested: true })An authored recast renders exactly as written, so import only work you own. json is your document with the fields the server adds; prefer it over your input. Importing the same script again returns created: false.
The run
estimate(input)
Quotes a run in credits (POST /v1/recast/estimate). It is free. The body is the same as create(), without workflowId, rightsAttested and clientCapabilities.
estimate(input: EstimateRecastInput): Promise<{ totalCredits?: number; breakdown?: Record<string, number> }>Prop
Type
const quote = await client.recast.estimate({ analysisJobId, fidelity: "faithful" })
console.log(quote.totalCredits, quote.breakdown)create(input)
Creates the run and buys the plan (POST /v1/recast). Quote it with estimate() first.
create(input: CreateRecastInput): Promise<{ recastId: string }>Prop
Type
const { recastId } = await client.recast.create({
workflowId,
analysisJobId,
fidelity: "faithful",
rightsAttested: true,
interactive: true,
clientCapabilities: ["sheet-gate"],
})Without workflowId, the call fails with 400 workflow_id_required; an unknown or foreign workflow fails with a 404.
get(recastId)
Reads the run (GET /v1/recast/:id). Poll it to follow progress. On an interactive run, interactive.next names the pending step or pick.
get(recastId: string): Promise<RecastRunSnapshot>Prop
Type
const run = await client.recast.get(recastId)
console.log(run.status, run.interactive)The snapshot has status, interactive, capabilities and, for a finished run with separate audio layers, audio. See Change the music mix.
start(recastId, opts?)
Starts rendering a run in the planned state (POST /v1/recast/:id/start). The plan's quote already covered it, and a repeated call changes nothing.
start(recastId: string, opts?: { segmentSec?: number; provider?: string }): Promise<{ gvpJobId?: string }>Prop
Type
const { gvpJobId } = await client.recast.start(recastId)resolveGate(recastId, input)
Answers a pending pick on an interactive run (POST /v1/recast/:id/select). The platform advances every other step itself, so a client only polls get() and answers picks. The pick itself is free.
resolveGate(recastId: string, input: ResolveRecastGateInput): Promise<Record<string, unknown>>Prop
Type
await client.recast.resolveGate(recastId, { gate: "music", musicPick: 1 })
await client.recast.resolveGate(recastId, { finishAuto: true })A pick opens only for the kinds your create() declared in clientCapabilities. The platform decides the others automatically.
Change the music mix
A finished recast can keep its music and the video's own sound as separate layers. When get() returns capabilities.audioLayers set to 1, the audio manifest describes them:
revisionidentifies the current audio. Every change needs it.presentlists the layers the recast has,musicandvideo.layersholds preview files for the layers that exist.bakedEffectiveGaindescribes the levels of the current download.pendingRescoredescribes a change still in progress. It survives a page reload.
Never make up a revision, and never guess the mix from a missing preview.
estimateRescore(recastId, input)
Quotes an audio change: a new mix, a new music track, or both (POST /v1/recast/:id/estimate-rescore). It is free and returns { credits, audioRevision, noOp }.
estimateRescore(recastId: string, input: EstimateRecastRescoreInput): Promise<{ credits: number; audioRevision: string; noOp: boolean }>Prop
Type
rescore(recastId, input)
Applies the audio change (POST /v1/recast/:id/rescore). Send the same operation you quoted, plus a new requestId, a UUID. Reuse that requestId only to retry the same request after a network failure.
rescore(recastId: string, input: EstimateRecastRescoreInput & { requestId: string }): Promise<
| { recastId: string; jobId: string }
| { recastId: string; noOp: true; audioRevision: string }
>Prop
Type
const { total: available } = await client.credits.balance()
const run = await client.recast.get(recastId)
if (run.capabilities?.audioLayers === 1 && run.audio) {
const operation = {
expectedAudioRevision: run.audio.revision,
mix: {
music: { gain: 55, muted: false },
video: { gain: 100, muted: false },
},
}
const quote = await client.recast.estimateRescore(recastId, operation)
if (!quote.noOp && available >= quote.credits) {
await client.recast.rescore(recastId, { ...operation, requestId: crypto.randomUUID() })
}
}The change runs as a job. Poll get(recastId) until audio.pendingRescore disappears and audio.revision changes. A change that would do nothing returns noOp: true without a job.
- An operation holds a mix, one music replacement (
audioUrlorsections), or a replacement with its mix. - With a replacement, send the complete mix you want. Leaving it out works only for the exact older default levels, and otherwise fails with
409 legacy_mix_mismatch. - A stale
expectedAudioRevisionfails with409 stale_audio_revision, and a second change while one runs fails with409 rescore_in_progress. Read the recast again, then quote again. - An invalid operation fails with a 400, such as
validation_error,unknown_section,duplicate_sectionorall_audio_silent. - A quote never reserves credits, and it returns the price even when your balance is too low.
Frequently asked questions
Related
Recast
Recast
Jobs and executions
Studio productions
Last updated on
Studio productions
Create Studio productions from a plan, edit them with operations, generate stills and clips, review planned frames, and share or copy them from TypeScript.
Pipelines
Start a story-to-video pipeline from TypeScript, follow it stage by stage, approve or reject stages, chat with the director, and read the finished timeline.