Skip to main content
Some Theo capabilities are too slow to return inline. research (plan → multi-source web search → synthesis) and video (model generation) run as background jobs: you enqueue them, get a job_id back immediately, then poll until they finish.
Do not send mode: "research" or mode: "video" to /completions or theo.stream(). Those run the work inline, which blows past the request timeout. The SDK throws a TheoUsageError immediately if you try. Use the dedicated methods below.

Lifecycle

  1. EnqueuePOST /api/v1/research or /api/v1/video returns 202 with { id, job_id, status: "queued", poll_url }.
  2. PollGET /api/v1/jobs/{job_id} returns the current status: queuedactivecompleted (or failed).
  3. Read the result — when status === "completed", result holds the typed payload.

SDK usage

waitForJob() does the polling loop for you, with an optional onProgress callback and an AbortSignal.
Video works the same way with theo.video() + waitForJob<VideoJobResult>().

Polling cadence & limits

  • Default poll interval is 2s; waitForJob gives up after 5 minutes by default (maxWaitMs) and throws a TheoTimeoutError. Raise maxWaitMs for long video renders.
  • Don’t poll faster than ~1s — it wastes rate-limit budget without finishing the job any sooner.

Cancellation

Pass an AbortSignal. When it fires, waitForJob stops polling and rejects with a TheoCancelledError (it does not cancel the server-side job — it just stops your client from waiting).

Result schemas

result is typed via the generic on job<T>() / waitForJob<T>(). The shapes mirror the REST payload (Get Job Status):
  • ResearchJobResult{ report, sources: { title, url }[], queries, sourceCount }
  • VideoJobResult{ videoUrl, model, engine, durationMs }
  • ImageJobResult{ imageUrl, model, engine }
  • DocumentJobResult{ title, format, downloadUrl, sizeBytes } Any model / engine fields are Theo-branded — raw upstream identifiers never appear.

Cost & usage

Job results do not yet carry a per-job cost_cents field. To attribute spend for research/video jobs, read the aggregated Usage report (theo.usage()), which includes a by_mode breakdown covering research and video.

Failures

When status === "failed", result is null and error holds the reason string. The enqueue call itself can also return 503 queue_unavailable if the job queue is temporarily down — retry shortly.