Guide

Polling projects

Creating a project returns immediately. The agent takes minutes. Here is how to wait for it well.

#Statuses

status is derived from what the agent is doing and what is in the workspace, never stored, so it is always current.

StatusMeaningKeep polling?
workingThe agent has a turn in flight: the first build, or a follow-up promptYes
readyThere is something to show. outputs holds the deliverablesNo
failedThe last turn produced nothing. error says what went wrongNo
emptyNothing in the workspace and no error. Rare; treat it like failedNo
busy is the same fact as status === "working", as a boolean. Follow-up prompts sent while busy is true are queued by default. Set delivery to "steer" to inject the instruction into the active run instead.

#How often

Every 10 seconds is right. Faster does not make the agent faster. Demo videos and decks usually finish in a few minutes, launch videos take longer because they run a full recon, script, build, and mix pass.

#A loop that handles every case

javascript
const TERMINAL = ["ready", "failed", "empty"]

async function waitFor(projectId, { intervalMs = 10_000, timeoutMs = 30 * 60_000 } = {}) {
  const deadline = Date.now() + timeoutMs

  while (Date.now() < deadline) {
    const res = await fetch(`https://api.trypitch.co/v1/projects/${projectId}`, {
      headers: { Authorization: `Bearer ${process.env.PITCH_API_KEY}` },
    })

    // Transient. Wait and try again rather than giving up on the project.
    if (res.status >= 500) {
      await sleep(intervalMs)
      continue
    }
    if (!res.ok) throw new Error((await res.json()).error.message)

    const project = await res.json()
    if (TERMINAL.includes(project.status)) return project

    await sleep(intervalMs)
  }

  throw new Error(`Project ${projectId} did not finish in time`)
}

const sleep = ms => new Promise(r => setTimeout(r, ms))

const project = await waitFor("cm4x8k2p90001abcd")
if (project.status !== "ready") throw new Error(project.error ?? project.status)
const latest = project.outputs.find(o => o.kind === "video" || o.kind === "pdf")
console.log(latest.url)

#Where the result lands

outputs is an array, newest first. Each entry is { kind, url, res?, label?, createdAt }. A project that has been edited a few times keeps every render, so take the first entry of the kind you want.

FlowOutput kindsAlso on the project
demo-videovideo (final), video with label: "raw" (the uncut recording)scenes
deckpdf, htmlslides
recording-editvideoscenes
launch-videovideo with res, once exported from the appscenes
A launch video is ready as soon as the film plays in the studio, with scenes filled in. The MP4 itself is rendered by the Export button in the app at the resolution requested, and lands in outputs when that finishes. REST and MCP both expose export start and status operations.

Videos also get a thumbnailUrl. shareUrl is set only if someone turned on sharing for that project in the app.

#Following up

You can send the agent another message at any time. It queues behind active work by default, edits in place, and adds the new render to outputs. This is how you fix a scene, change the voice, tighten the cut, or ask for a different take. It costs nothing.

bash
curl -X POST https://api.trypitch.co/v1/projects/cm4x8k2p90001abcd/prompt \
  -H "Authorization: Bearer pk_your_key" \
  -H "Content-Type: application/json" \
  -d '{ "text": "Swap the music for something calmer and drop scene 3.", "scene": "s3" }'

scene (an id from scenes) and slide (a 1-based index) are optional and tell the agent what you are pointing at, the way clicking a scene in the app does. The response is the project with status: "working"; poll as before.

#Listing projects

If you lost an id, list them. Newest first.

bash
curl "https://api.trypitch.co/v1/projects?limit=10" \
  -H "Authorization: Bearer pk_your_key"

#Scenes and slides

Video flows expose scenes from the live workspace; decks expose slides. They are more useful than a percentage: you can see how far the agent got, and you can target a follow-up prompt at one of them.

json
{
  "id": "cm4x8k2p90001abcd",
  "status": "ready",
  "scenes": [
    { "id": "s1", "index": 1, "start": 0, "end": 6.2, "dur": 6.2, "label": "Meet Acme", "type": "hero" }
  ],
  "outputs": []
}