Core concepts

Jobs & polling

The unified async job model: type-prefixed ids, neutral statuses, and one poll endpoint.

Job ids

Every async endpoint returns a job with a type-prefixed id, and every job, regardless of pipeline, is polled at one dispatcher:

Prefixes
vid_
video
POST /videos
song_
song
POST /songs
code_
code build
POST /code-builds
res_
research
POST /research
space_
space
POST /real-world/spaces (a room from photos or a video, or a one-photo space)
deck_
presentation
POST /presentations (the job rides the 201 body as job) and PATCH /projects/{id} with new slides

Polling requires the read or ai scope (either works), so the key that created a job can always poll it. Research additionally keeps a legacy alias at GET /research/{jobId} that accepts raw or res_-prefixed ids and returns its original response shape; prefer GET /jobs/{id}.

Statuses

Statuses are provider-neutral: queued then processing then complete or failed. The envelope also carries the pipeline's rawStatus for fidelity and an optional progressMessage.

Polling

curl
curl https://www.opencharts.com/api/v1/jobs/vid_6870f3abc \
  -H "Authorization: Bearer $OPENCHARTS_API_KEY"

Completed jobs carry a result with stable, absolute asset URLs; failed jobs carry an error string. Poll every few seconds with backoff; renders can take minutes. One exception to "result on complete": a space job also carries its result while processing as soon as its first tier has landed (stage: preview: a room's walkable quick room, or a one-photo space's 360 preview), so you can show the room before the final tier finishes.

Results by type

video result
{
  "videoUrl": "https://www.opencharts.com/api/canvas-video/...",
  "thumbnailUrl": "https://...",
  "aspectRatio": "16:9",
  "modelTier": "fast",
  "durationSeconds": 8
}
song result
{
  "title": "Neon Skyline",
  "songId": "6870f4...",
  "versions": [
    {
      "id": "a",
      "audioUrl": "https://...",
      "coverImageUrl": "https://...",
      "durationSeconds": 172,
      "engine": "Theo Symphony"
    }
  ]
}
code build result
{ "codeProjectId": "6870f5...", "name": "Landing page", "framework": "html" }
research result
{
  "content": "# Report...",
  "sources": [ { "url": "https://...", "title": "..." } ],
  "searchCount": 14,
  "readCount": 9,
  "totalTokens": 48213
}
presentation (deck) result
{
  "projectId": "6870f6...",
  "name": "Q4 Board Deck",
  "kind": "create",
  "slideCount": 11,
  "slidesTotal": 12,
  "partial": true,
  "failedSlides": 1,
  "openUrl": "https://www.opencharts.com/presentations/6870f6..."
}

A deck is complete once its slides are built. partial: true means some slides did not build: the deck keeps a placeholder for each, and opening it offers to generate the missing ones. A rewrite (kind: regenerate) keeps the existing slides until the new ones are ready.

space result (stage preview while processing, stage space once complete)
{
  "stage": "space",
  "imagined": true,
  "source": "photos",
  "photoCount": 4,
  "panoramaUrl": "https://www.opencharts.com/api/canvas-video/...",
  "spaceUrl": "https://www.opencharts.com/api/canvas-video/...",
  "spaceFullUrl": "https://www.opencharts.com/api/canvas-video/...",
  "format": "spz",
  "metricScale": 1.38,
  "floorOffsetM": 1.351,
  "colliderUrl": "https://www.opencharts.com/api/canvas-video/...",
  "dimensions": null,
  "engine": "Theo Real World 1.1"
}

Prefer webhooks

Instead of polling, register a webhook for the matching *.completed / *.failed events and receive a signed delivery the moment a job finishes. See Webhooks.