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.
| Status | Meaning | Keep polling? |
|---|---|---|
working | The agent has a turn in flight: the first build, or a follow-up prompt | Yes |
ready | There is something to show. outputs holds the deliverables | No |
failed | The last turn produced nothing. error says what went wrong | No |
empty | Nothing in the workspace and no error. Rare; treat it like failed | No |
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
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.
| Flow | Output kinds | Also on the project |
|---|---|---|
demo-video | video (final), video with label: "raw" (the uncut recording) | scenes |
deck | pdf, html | slides |
recording-edit | video | scenes |
launch-video | video with res, once exported from the app | scenes |
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.
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.
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.
{
"id": "cm4x8k2p90001abcd",
"status": "ready",
"scenes": [
{ "id": "s1", "index": 1, "start": 0, "end": 6.2, "dur": 6.2, "label": "Meet Acme", "type": "hero" }
],
"outputs": []
}