Query a real consumer AI search engine and get the answer users actually see — plus the structured evidence behind it — as JSON. This guide gets you from an API key to a parsed Envelope in about five minutes.
1

Get an API key

Grab a key from the dashboard, then export it so the examples below just work.
  1. Open the dashboard and go to Settings → API Keys.
  2. Create a key and copy it (you only see the full value once).
  3. Export it in your shell:
export AISEARCH_API_KEY="sk_live_..."
Verify connectivity with the unauthenticated health check:
curl https://api.aisearchapi.dev/v1/health
Response
{ "status": "ok", "schemaVersion": "1.0" }
New accounts start with 500 free credits, no card required. A capture is charged only on success — an empty or failed capture costs nothing. See pricing.
2

Make your first search

Submit a query and at least one surface. By default the API is async: it returns 202 immediately with a parent job and one child per surface × region.
curl https://api.aisearchapi.dev/v1/search \
  -H "Authorization: Bearer $AISEARCH_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "query": "best crm for startups",
    "surfaces": ["chatgpt"]
  }'
202 Accepted
{
  "jobId": "job_8t2q",
  "status": "processing",
  "children": ["job_8t2q.chatgpt.us"]
}
The response carries a Location header pointing at the parent job. Each entry in children is a child id — a dotted id (job_8t2q.chatgpt.us) that resolves to the Envelope for one surface in one region.
3

Poll for the result

Fetch a job with GET /v1/jobs/:id. A child id (dotted) returns the canonical Envelope. Poll until the job reaches any terminal state — completed, partial, failed, canceled, or expired — and never past it.
CHILD="job_8t2q.chatgpt.us"

while true; do
  BODY=$(curl -s "https://api.aisearchapi.dev/v1/jobs/$CHILD" \
    -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 .
      break ;;
  esac
  sleep 2
done
Polling is fine for a single surface. For fan-out across many surfaces and regions, prefer webhooks — you get a POST on each child’s terminal state instead of polling N jobs.
4

Read the Envelope

A completed child returns four sections: job, provenance, answer, and evidence.
GET /v1/jobs/job_8t2q.chatgpt.us
{
  "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": []
  }
}

Key fields to read first

FieldWhat it gives you
answer.textThe plain-text answer the user sees.
answer.markdownThe same answer with formatting; always populated.
answer.blocks[].referenceIdsWhich evidence.sources each block cites.
evidence.sources[]Every source, each with a role (cited / retrieved / related), a cited flag, and charRanges mapping it back into the answer.
evidence.fanOut.queriesThe follow-up queries the surface ran under the hood.
evidence.mentionsBrands and entities named in the answer.
provenance.modelWhich model produced it, whether it was inferred, and a confidence score.
provenance.surfacePresentfalse means the surface returned nothing (see below).
When a surface returns nothing, the job still reaches completed with provenance.surfacePresent: false, an empty answer, and a surface_absent entry in job.warnings. Absence is a valid, billable-free result — the capture costs no credits.

Sync mode

Skip polling for a single surface. Add ?mode=sync (or send the header Prefer: wait) to get the Envelope inline with a 200 instead of a 202.
curl "https://api.aisearchapi.dev/v1/search?mode=sync" \
  -H "Authorization: Bearer $AISEARCH_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "query": "best crm for startups",
    "surfaces": ["chatgpt"]
  }'
The convenience alias POST /v1/search/:surface is sync-by-default for one surface and accepts prompt (an alias of query) plus a flat country:
curl https://api.aisearchapi.dev/v1/search/chatgpt \
  -H "Authorization: Bearer $AISEARCH_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "prompt": "best crm for startups", "country": "US" }'
Sync mode resolves one surface at a time. For more than one surface — or when you hit CONCURRENCY_LIMIT_EXCEEDED — use the default async flow with polling or webhooks.

Fan out across surfaces & regions

Add more surfaces and regions to a single search. The API creates one child per surface × region, and the parent tracks them all.
curl https://api.aisearchapi.dev/v1/search \
  -H "Authorization: Bearer $AISEARCH_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "query": "best crm for startups",
    "surfaces": ["chatgpt", "perplexity", "google_ai_overview"],
    "regions": [
      { "country": "US" },
      { "country": "GB", "language": "en" }
    ]
  }'
202 Accepted
{
  "jobId": "job_8t2q",
  "status": "processing",
  "children": [
    "job_8t2q.chatgpt.us",
    "job_8t2q.chatgpt.gb",
    "job_8t2q.perplexity.us",
    "job_8t2q.perplexity.gb",
    "job_8t2q.google_ai_overview.us",
    "job_8t2q.google_ai_overview.gb"
  ]
}
Read the parent id (no dots) to see the roll-up:
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.perplexity.gb", "surface": "perplexity", "region": "GB", "status": "processing" }
  ]
}
The parent is completed when all children complete, failed when all fail, and partial on a mix.

Next steps

Webhooks

Get a signed POST on each child’s terminal state instead of polling.

Batch

Submit up to 500 searches in one request.

Errors

Codes, statuses, and the rate-limit headers to respect.

SDKs

Typed clients for Node and Python.