Jobs and resumable polling

Persist asynchronous job IDs, poll with bounded backoff, and stop on every terminal status.

Playbooks, Audiences, Brand research, Prospecting, and Documents return asynchronous jobs. The shared type field selects the typed input and result: playbooks uses PublicPlaybookJobResponse, audiences uses PublicAudienceJobResponse, brands uses PublicBrandJobResponse, prospecting uses ProspectingJobResponse, and documents uses DocumentJobResponse.

Lifecycle#

StatusTerminalClient behavior
queuedNoWait before polling again.
runningNoReport progress if useful and keep polling.
succeededYesValidate and render the typed result.
failedYesStop and handle error_code when present, then surface the job's error message.
canceledYesStop; there is no public cancel operation in the initial contract.

Do not wait only for succeeded, because that creates infinite loops after failures or cancellation.

Resumable polling algorithm#

  1. After a successful create response, persist id before the first poll.
  2. Poll GET /v1/jobs/{job_id} after 2 seconds.
  3. Back off exponentially with jitter, capped at 10 seconds.
  4. Set a local deadline appropriate to the caller. A deadline stops the client, not server work.
  5. Keep the ID when the deadline expires and resume it later rather than creating duplicate work.
  6. Stop on succeeded, failed, or canceled.

Before the create request, persist an Idempotency-Key. If the connection fails before the response arrives, repeat the exact job-creation operation and input with that key during its 24-hour replay window. This recovers the original response instead of creating duplicate work.

The Quickstart includes a complete cURL implementation. Tested Python and TypeScript implementations under docs/examples add jitter and use the same terminal-state rules.

Ownership and billing#

A job is visible only to a key for its owning project. A missing or cross-project ID returns not_found. Creating a job records accepted work and its eventual capability usage. Poll requests are subject to request rate limits but are not billed as new generation requests.

Job creation requires a positive spendable credit balance, but does not reserve or estimate the run's price. Settlement uses the actual provider cost after execution. If that amount exceeds the remaining balance, the platform consumes the balance without taking it negative, triggers configured auto recharge, and terminates the job as failed with error_code insufficient_credits; result remains null.

progress is coarse and is not a completion-time promise. Do not build an SLA from it. Every public job includes expires_at, exactly 30 days after created_at. Job metadata, input, and results remain readable through that timestamp. Store any result your application needs longer.

After expires_at, direct job and Audience candidate reads return 410 with error.code job_expired. Cleanup removes retained input, result, failure detail, stages, and private diagnostics, but preserves the minimal ownership tombstone plus usage, cost, credit, and audit ledgers. Expiry does not cancel work, reverse billed usage, or erase accounting history.

Poll errors#

Safe GET requests may be retried for rate_limit_exceeded after Retry-After and for documented transient 5xx failures with bounded exponential backoff. Fix authentication, entitlement, quota, and budget errors before retrying. See Errors.

Job retrieval is OpenAPI operation get_job_v1_jobs__job_id__get. Its response is the same type-discriminated union of Playbooks, Audiences, Brands, Prospecting, and Documents Job schemas. Documents results contain file references; download their bytes through the authenticated endpoint documented in Documents before the Job expires.