Submit a query to one or more AI search surfaces.
  • One surface, no webhook (the hello-world case) runs synchronously by default: the call returns the terminal Envelope in one 200.
  • Multiple surfaces, a webhook, or ?mode=async use the durable path: 202 immediately with a parent job and one child per surface × region. Poll GET /v1/jobs/:id or register a webhook.
POST https://api.aisearchapi.dev/v1/search
Every request needs Authorization: Bearer <API_KEY> and Content-Type: application/json.

Query parameters

mode
string
sync forces the bounded inline path; async forces the durable 202 + poll path. Omitted: a single-surface no-webhook request runs sync, everything else async. The header Prefer: wait=30 is equivalent to ?mode=sync.
view
string
flat projects each Envelope of a sync response to the five-field flat shape (surface, status, text, markdown, sources). One child → the flat object directly; several children → { "results": [...] }.

Body

query
string
required
The prompt to run. This is the exact text sent to each surface. prompt is accepted as an alias.
surfaces
string[]
required
One or more surfaces to capture. Each surface produces one child job per region.Allowed values: chatgpt, claude, perplexity, gemini, copilot, google_ai_overview, google_ai_mode, google_search, google_news.
regions
object[]
Where to run each surface — at most 10 per request. One child is created per surface × region. Omitted: one untargeted (GLOBAL) child per surface. A bare string like "US" is accepted as sugar for { "country": "US" }.
method
string
default:"auto"
How to acquire each surface: auto (browser-first routing), or a pin — official-api, managed-vendor, own-fleet. A pin a surface cannot serve returns 422 with the supported methods named in the message.
webhook
object
Receive a signed POST on each child’s terminal state instead of polling. Setting a webhook forces the async path. 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 (202 + Idempotent-Replayed: true); 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" }]
  }'

Response 202 Accepted (async)

The parent job id has no dots. Each entry in children is a dotted 4-segment child id — <jobId>.<surface>.<method>.<regionKey> — where the method is what auto actually routed to and the region key is the lowercased country[:city][:language] (or GLOBAL when untargeted).
{
  "jobId": "job_8t2q",
  "status": "processing",
  "children": [
    "job_8t2q.chatgpt.own-fleet.us",
    "job_8t2q.perplexity.own-fleet.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. Reading a child id returns its Envelope.
A poll loop should stop on any terminal state — prefer webhooks over polling for fan-out.

Response 200 OK (sync)

A sync call returns the parent id, its rollup status, and the full Envelope per child:
{
  "jobId": "job_8t2q",
  "status": "completed",
  "children": [
    {
      "job": {
        "id": "job_8t2q.chatgpt.own-fleet.us",
        "query": "best crm for startups",
        "surface": "chatgpt",
        "method": "own-fleet",
        "region": "us",
        "status": "completed",
        "warnings": [],
        "requestedAt": "2026-06-30T17:02:11Z",
        "completedAt": "2026-06-30T17:02:23Z",
        "artifacts": { "rawKey": "raw/...", "proofHtmlKey": "proof/...", "screenshotKey": null }
      },
      "provenance": {
        "method": "scrape",
        "acquisition": "own-fleet",
        "official": false,
        "model": { "providerId": null, "observedLabel": "GPT-5", "inferred": true, "confidence": 0.8 },
        "webSearch": { "enabled": true, "known": true },
        "surfacePresent": true,
        "region": { "requested": "us", "effective": "us" },
        "fidelity": "consumer_ui",
        "capturedAt": "2026-06-30T17:02:23Z"
      },
      "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://example.com/best-crms", "title": "Best CRMs for startups", "role": "cited", "cited": true, "charRanges": [[0, 58]] }
        ],
        "fanOut": { "provenance": "observed", "queries": ["best crm for startups 2026"] },
        "mentions": null,
        "shopping": null,
        "ads": null
      },
      "raw": { "ref": "raw/..." }
    }
  ]
}
With ?view=flat the same call returns just { surface, status, text, markdown, sources }.
Sync mode holds the connection open until the capture finishes and is subject to 429 CONCURRENCY_LIMIT_EXCEEDED. For more than one surface, prefer the async default.
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 — sync by default like any single-surface call. 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?view=flat" \
  -H "Authorization: Bearer $AISEARCH_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "prompt": "best crm for startups",
    "country": "US"
  }'

Idempotency

Include idempotencyKey in the body to make retries safe.
ScenarioResult
First request with a keyThe job is created normally.
Same key + same body202 with header Idempotent-Replayed: true — the original job is replayed, no new work, no double billing.
Same key + different body409 IDEMPOTENCY_CONFLICT.
curl -X POST "https://api.aisearchapi.dev/v1/search?mode=async" \
  -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 flat shape { "code", "error", "request_id", "docs_url", "details"? }. Validation messages name the offending field and the allowed values.
400
{
  "code": "UNSUPPORTED_SURFACE",
  "error": "surface 'grok' is not supported; valid surfaces: chatgpt, claude, perplexity, gemini, copilot, google_ai_overview, google_ai_mode, google_search, google_news",
  "request_id": "req_9f2c1a7e4b5d48f1a3c6e8d0b2a4f6c8",
  "docs_url": "https://docs.aisearchapi.dev/guides/errors#unsupported_surface"
}
CodeStatusWhen
AUTH_INVALID401Missing, bad, or revoked API key.
VALIDATION_FAILED400A field failed validation — the message names the field and allowed values.
UNSUPPORTED_SURFACE400A value in surfaces (or the alias :surface) isn’t a surface.
INSUFFICIENT_CREDITS402Not enough credits; nothing is spawned.
IDEMPOTENCY_CONFLICT409Same idempotencyKey reused with a different body.
UNSUPPORTED_METHOD_FOR_SURFACE422The pinned method has no v1 path on that surface — the message names what IS supported.
TOO_MANY_REGIONS422More than 10 regions in one request.
RATE_LIMIT_EXCEEDED429Account request rate exceeded.
CONCURRENCY_LIMIT_EXCEEDED429Too many in-flight sync requests.
QUEUE_CAPACITY_EXCEEDED429Async queue is full.
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-Request-Idevery responseCorrelation id; matches request_id in error bodies. Send your own to thread it through.
X-AISearch-Versionevery responseEnvelope schema version that served the request.
X-RateLimit-Limit / -Remaining / -Resetsubmits + 429Live admission-bucket state for your key.
Retry-After429Seconds to wait before retrying.
X-Concurrency-Limit / -Running / -QueuedsubmitsSync concurrency state for your key.
Idempotent-Replayedreplaystrue when an idempotent re-submit replayed the original job.

Read a job

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

The Envelope

Every field in the canonical per-surface result.