GET /v1/jobs/:id reads the current state of a job. There are two kinds of id, and they return two different shapes.
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 "
{
"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" }
]
}
One entry per surface × region. The child id — fetch it to read the Envelope.
The surface for this child.
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 "
{
"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.
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
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.
{
"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.