Submit a query to one or more AI search surfaces. By default the request is asynchronous: it returns 202 immediately with a parent job and one child per surface × region. Poll GET /v1/jobs/:id or register a webhook to receive each child’s Envelope on completion.
POST https://api.aisearchapi.dev/v1/search
Every request needs Authorization: Bearer <API_KEY> and Content-Type: application/json.

Body

query
string
required
The prompt to run, 1–10000 characters. This is the exact text sent to each surface.
surfaces
string[]
required
One or more surfaces to capture. At least one is required. Each surface produces one child job per region.Allowed values: chatgpt, claude, perplexity, gemini, copilot, google_ai_overview, google_ai_mode.
regions
object[]
default:"[{ \"country\": \"US\" }]"
Where to run each surface. One child is created per surface × region.
priority
number
default:"5"
Queue priority, 1–10. Higher values are scheduled ahead of lower ones within your account.
webhook
object
Receive a signed POST on each child’s terminal state instead of polling. Recommended for fan-out.
idempotencyKey
string
Safe-retry key. This is a body field, not a header. Reusing the same key with the same body replays the original job; reusing it with a different body returns 409 IDEMPOTENCY_CONFLICT.

Request

curl -X POST 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"],
    "regions": [{ "country": "US" }],
    "priority": 5
  }'

Response 202 Accepted

The parent job id has no dots. Each entry in children is a dotted child id you can read individually.
{
  "jobId": "job_8t2q",
  "status": "processing",
  "children": [
    "job_8t2q.chatgpt.us",
    "job_8t2q.perplexity.us"
  ]
}
jobId
string
The parent job id (dotless). Read it with GET /v1/jobs/job_8t2q to see the roll-up status and per-child state.
status
string
Always processing on submit. Terminal states are completed, partial, failed, canceled, expired.
children
string[]
One dotted child id per surface × region, e.g. job_8t2q.chatgpt.us. Reading a child id returns its Envelope.
The Location response header points at the parent job. A poll loop should stop on any terminal state — prefer webhooks over polling for fan-out.

Sync variant

For a single surface you can get the Envelope inline with 200 instead of polling. Add ?mode=sync to the URL or send the header Prefer: wait.
Sync mode is for one surface at a time. It holds the connection open until the capture finishes and is subject to CONCURRENCY_LIMIT_EXCEEDED (429) and SURFACE_TIMEOUT (504).
curl -X POST "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"],
    "regions": [{ "country": "US" }]
  }'

Response 200 OK

Returns the full Envelope for the single surface. See The Envelope for every field.
{
  "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": []
  }
}
If a surface returns nothing, the job still completes: provenance.surfacePresent is false, job.warnings carries a surface_absent warning, and answer is empty. An empty capture costs no credits.

Per-surface alias

POST /v1/search/:surface targets one surface and is sync by default — no ?mode=sync needed. It accepts two convenience fields:
  • prompt — alias of query.
  • country — flat sugar for regions: [{ "country": "..." }].
curl -X POST 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"
  }'
Returns 200 with the Envelope, exactly like the sync variant above.

Idempotency

Include idempotencyKey in the body to make retries safe.
ScenarioResult
First request with a key202 — a new job is created.
Same key + same body200 with header Idempotent-Replayed: true — the original job is replayed, no new work.
Same key + different body409 IDEMPOTENCY_CONFLICT.
curl -X POST 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"],
    "idempotencyKey": "crm-report-2026-06-30"
  }'

Errors

Every error uses the shape { "error": { "code", "message", "status", "details"? } }.
CodeStatusWhen
AUTH_MISSING401No Authorization header.
AUTH_INVALID401Bad or revoked API key.
VALIDATION_FAILED400Body failed validation; see details[].
UNSUPPORTED_SURFACE400A value in surfaces (or the alias :surface) is not requestable.
IDEMPOTENCY_CONFLICT409Same idempotencyKey reused with a different body.
RATE_LIMIT_EXCEEDED429Account request rate exceeded.
CONCURRENCY_LIMIT_EXCEEDED429Too many in-flight sync requests.
QUEUE_CAPACITY_EXCEEDED429Async queue is full.
SURFACE_TIMEOUT504A sync capture did not finish in time.
429 responses carry Retry-After alongside X-RateLimit-Limit, X-RateLimit-Remaining, and X-RateLimit-Reset. See Errors for the full catalog.

Response headers

HeaderOnMeaning
X-AISearch-Versionevery responseAPI version that served the request.
Location202URL of the created parent job.
Idempotent-Replayedreplaystrue when an idempotent request replayed an existing job.
X-RateLimit-Limitrate-limitedRequest ceiling for the current window.
X-RateLimit-Remainingrate-limitedRequests left in the current window.
X-RateLimit-Resetrate-limitedWhen the window resets.
Retry-After429Seconds to wait before retrying.

Read a job

Poll a parent or fetch a single child’s Envelope.

The Envelope

Every field in the canonical per-surface result.