GET /v1/jobs/:id reads the current state of a job. There are two kinds of id, and they return two different shapes.
id
string
required
A job id. Parent ids have no dots (job_8t2q). Child ids are dotted (job_8t2q.chatgpt.us).

Parent vs. child ids

When you submit a search, you get back a parent job and one child per surface × region.
  • A parent id (job_8t2q) returns a rollup: the parent’s status plus a list of its children. Use it to track fan-out progress.
  • A child id (job_8t2q.chatgpt.us) returns the canonical Envelope — the answer, provenance, and evidence for exactly one surface in one region.
Child ids follow the form job_<id>.<surface>.<region>. You get them from the children array on the submit response (202) or from a parent read.

Parent response

curl https://api.aisearchapi.dev/v1/jobs/job_8t2q \
  -H "Authorization: Bearer $AISEARCH_API_KEY"
Parent
{
  "job": { "id": "job_8t2q", "status": "processing" },
  "children": [
    { "id": "job_8t2q.chatgpt.us", "surface": "chatgpt", "region": "US", "status": "completed" },
    { "id": "job_8t2q.claude.us", "surface": "claude", "region": "US", "status": "processing" },
    { "id": "job_8t2q.perplexity.us", "surface": "perplexity", "region": "US", "status": "queued" }
  ]
}
job
object
children
object[]
One entry per surface × region.

Child response (the Envelope)

Fetch a child id to get the full result for one surface.
curl https://api.aisearchapi.dev/v1/jobs/job_8t2q.chatgpt.us \
  -H "Authorization: Bearer $AISEARCH_API_KEY"
Envelope
{
  "job": {
    "id": "job_8t2q.chatgpt.us",
    "query": "best crm for startups",
    "surface": "chatgpt",
    "region": "US",
    "status": "completed",
    "warnings": [],
    "requestedAt": "2026-06-30T17:02:11Z",
    "completedAt": "2026-06-30T17:02:23Z"
  },
  "provenance": {
    "model": { "providerId": "openai", "observedLabel": "GPT-5", "inferred": false, "confidence": 0.98 },
    "webSearch": { "enabled": true, "known": true },
    "region": { "requested": "US", "effective": "US" },
    "surfacePresent": true
  },
  "answer": {
    "text": "For startups, the top CRMs are HubSpot, Attio and Pipedrive...",
    "markdown": "For startups, the top CRMs are **HubSpot**, **Attio** and **Pipedrive**...",
    "blocks": [ { "type": "paragraph", "text": "...", "referenceIds": [1] } ]
  },
  "evidence": {
    "sources": [
      { "id": 1, "url": "https://...", "title": "Best CRMs for startups", "role": "cited", "cited": true, "charRanges": [[0, 58]], "quote": "..." }
    ],
    "fanOut": { "queries": ["best crm for startups 2026", "hubspot vs attio"] },
    "mentions": ["HubSpot", "Attio", "Pipedrive"],
    "shopping": [],
    "ads": []
  }
}
The Envelope has four sections — job, provenance, answer, and evidence. See The Envelope for a full field-by-field reference.
If a surface returns nothing, the child still finishes as completed with provenance.surfacePresent: false, a surface_absent warning, and an empty answer. An empty capture costs no credits.

Status values

A job is always in exactly one status.
Active statuses
queued | processing
The job is still running. Keep polling, or wait for a webhook.
Terminal statuses
completed | partial | failed | canceled | expired
The job is done. Stop polling on any of these.
For a parent, the rollup is derived from its children: completed when all children completed, failed when all failed, and partial on a mix.
A poll loop must stop on any terminal state — not just completed. Treating partial, failed, canceled, or expired as “keep waiting” will loop forever.

Polling loop

Poll the parent until it reaches a terminal state, then read each child.
Poll a parent until terminal
JOB_ID="job_8t2q"

while true; do
  BODY=$(curl -s "https://api.aisearchapi.dev/v1/jobs/$JOB_ID" \
    -H "Authorization: Bearer $AISEARCH_API_KEY")
  STATUS=$(echo "$BODY" | jq -r '.job.status')
  echo "status: $STATUS"

  case "$STATUS" in
    completed|partial|failed|canceled|expired)
      echo "$BODY" | jq -r '.children[].id' | while read -r CHILD; do
        curl -s "https://api.aisearchapi.dev/v1/jobs/$CHILD" \
          -H "Authorization: Bearer $AISEARCH_API_KEY" | jq '.answer.text'
      done
      break
      ;;
  esac
  sleep 2
done
For fan-out across many surfaces or regions, prefer webhooks over polling. You get a signed POST the moment each child reaches a terminal state — no loop, no wasted requests, and each child’s Envelope is delivered inline.

Errors

404
JOB_NOT_FOUND
No job exists for the given id, or it has expired. Check the id — remember that parent ids have no dots and child ids do.
404
{
  "error": {
    "code": "JOB_NOT_FOUND",
    "message": "No job found for id job_8t2q.chatgpt.us",
    "status": 404
  }
}
See the full error reference for every code and the shared error shape.

Submit a search

Start a job with POST /v1/search.

The Envelope

Every field in a child result.