Every AI Search API call follows the same shape: a JSON POST to the base URL, authenticated with your API key. You describe what to ask (a query) and where to ask it (one or more surfaces), and the API captures the answer — browser-first, from the live AI apps — then normalizes every result into one consistent Envelope.

The essentials

Every request shares three constants:
  • Base URLhttps://api.aisearchapi.dev
  • Auth — an Authorization: Bearer <API_KEY> header on every call
  • Format — JSON in, JSON out (Content-Type: application/json)
Keep your API key server-side. Never ship it in client-side code or commit it to source control. The examples below read it from an AISEARCH_API_KEY environment variable.

The request body

The core endpoint is POST /v1/search. At minimum it needs a query and at least one surface in surfaces.
query
string
required
What to ask — a non-empty string, up to 2000 characters (longer is 422 QUERY_TOO_LONG). prompt is accepted as an alias.
surfaces
string[]
required
Which AI apps to capture from. One or more of chatgpt, claude, perplexity, gemini, copilot, google_ai_overview, google_ai_mode, google_search, google_news. See Surfaces for which are live today (gemini currently has no v1 path and returns 422).
regions
object[]
Where to run each capture: { country, state?, city?, language? }, with country as an ISO-3166 alpha-2 code. At most 10 per request. When omitted, each surface runs one untargeted (GLOBAL) capture. See Regions.
webhook
object
{ url, secret? } — get a signed POST when each capture finishes instead of polling. Setting a webhook forces the async path. See Webhooks.
include
object
Output-format flags: { markdown?: boolean = true, html?: boolean = false }. Set markdown: false to trim answer.markdown from the result (answer.text is always present); set html: true to add a proof-of-page HTML URL on consumer-UI surfaces. Unknown include keys are rejected with 400 VALIDATION_FAILED. See Output formats.
idempotencyKey
string
A key you supply to safely retry the same request without creating duplicate work. A body field, not a header.
Send only the documented fields. Unknown top-level fields are rejected with 400 VALIDATION_FAILED (the message names the offender) — the API fails loud rather than silently dropping a field that looks like a control.

One request fans out

A single POST /v1/search produces one child capture per surface × region. Ask two surfaces across two regions and you get four children — each captured independently, each normalized to the same Envelope. On the async path, the response is a parent job whose children[] are the individual captures:
Each child id has three dot-separated segments — job_<id>.<surface>.<regionKey> — where the region key is the lowercased country[:city][:language] (or GLOBAL when untargeted). Fetch a child directly with GET /v1/jobs/:id to get its Envelope. Every Envelope carries the normalized answer alongside structured evidence — the sources[] behind the answer, the fanOut queries the surface ran, and any brand mentions — at no extra credit cost. See Output formats for the full shape.

Two response shapes

The same endpoint can resolve two ways, depending on what you submit.
A request for one surface with no webhook runs synchronously by default: the API holds the connection while the capture runs and returns 200 with the finished result inline — the parent id, its rollup status, and the full Envelope per child. You can force this path explicitly with ?mode=sync or the header Prefer: wait=30, and simplify the shape with ?view=flat.
Both shapes report billing. Metered (restricted-key) submits return X-Credits-Charged and X-Credits-Remaining headers on the sync 200 and the async 202, and a credits object ({ creditsToCharge, creditsCharged }) rides along on the sync result and each child job. Billing is success-only. See Credits.

Synchronous vs. asynchronous

The convenience alias POST /v1/search/:surface (for example /v1/search/chatgpt) targets one surface — sync by default like any single-surface call — and accepts prompt as an alias of query plus a flat country. It’s the quickest way to hit one surface.

A minimal request

This asks ChatGPT and Claude the same question. Because it names two surfaces, it returns a 202 parent job.
Drop "claude" from surfaces and the same call becomes synchronous automatically — a 200 with the finished ChatGPT result inline.

Next steps

Synchronous requests

Get one surface’s result back in the same call.

Asynchronous requests

Fan out, then poll the parent job for results.

Webhooks

Skip polling — get notified when captures finish.