Every search you submit becomes a job. A request that spans multiple AI surfaces or carries a webhook runs asynchronously: you get an immediate acknowledgment, and the actual captures run durably in the background. (A single-surface request with no webhook runs synchronously by default — add ?mode=async to force the durable path for it too.)

The async model

An async POST /v1/search returns 202 Accepted right away with a parent job. The parent fans out into one child per surface × region — that’s the unit of work that actually runs a capture and produces an Envelope.
The 202 response names the parent and lists its children — here, 2 surfaces × 2 regions = 4 children:
The response also carries X-Request-Id, X-AISearch-Version, and the live X-RateLimit-* / X-Concurrency-* admission headers. For a single surface where you’d rather block and get the result inline, that’s the sync default (or force it with ?mode=sync / Prefer: wait=30).

Parent vs child ids

The shape of a job id tells you what you’ll get back when you read it.
Parent id
no dots — e.g. job_8t2q
Resolves to the fan-out summary: { job, children[] }. Use it to see overall status and enumerate children.
Child id
dotted — e.g. job_8t2q.chatgpt.us
Shaped job_<id>.<surface>.<regionKey>: three segments, the surface and the lowercased region key (country[:city][:language], or GLOBAL when untargeted). Resolves to the canonical Envelope, the answer and its provenance, for exactly one surface in one region.
Both are read through the same endpoint, GET /v1/jobs/:id. The id you pass decides which shape comes back. Never construct child ids by hand — use the ids the API returns.

Lifecycle states

A polling loop must stop on any terminal state — not just completed. Treat completed, partial, failed, canceled, and expired as “done.”

Parent rollup rules

A parent’s status is derived from its children:
  • completed — every child completed.
  • failed — every child failed.
  • partial — a mix of completed and failed children.
While any child is still in flight, the parent stays processing.
A child can complete even when the surface returned nothing. In that case the Envelope has provenance.surfacePresent: false, an empty answer, and a surface_absent warning — the job is still completed, and an empty capture is not charged. See The Envelope.

How to read results

You have two ways to collect results after the 202.
1

Poll the parent for status

GET /v1/jobs/job_8t2q returns the current parent status and every child’s status. Stop as soon as the parent reaches a terminal state.
2

Read each child Envelope

For every child that reached completed (or partial’s completed children), GET /v1/jobs/job_8t2q.chatgpt.us to fetch its full Envelope.
A parent GET looks like this:
Then read a finished child:

A minimal poll loop

Node
For fan-out — many surfaces, many regions — prefer webhooks over polling. Register webhook: { url, secret } on the search and you’ll receive an HMAC-signed POST as each child reaches its terminal state, with the full Envelope in the payload. It’s fewer requests and lower latency than a poll loop. See Webhooks.

The Envelope

The canonical shape a child job resolves to.

Webhooks

Get pushed each child’s result instead of polling.