Synchronous mode returns the Envelope directly in the HTTP response. You make one request, the call blocks for the capture duration, and you get a 200 with the parsed result. No jobId, no polling, no webhook. Use sync when a human is waiting on a single surface × region. For anything wider — multiple surfaces, multiple regions, or work you don’t want to block on — use asynchronous requests.

Two ways to go synchronous

Add ?mode=sync

Send a normal POST /v1/search body with exactly one surface and one region, plus ?mode=sync (or the header Prefer: wait).

Use the surface alias

POST /v1/search/:surface (e.g. /v1/search/chatgpt) is synchronous by default. It accepts prompt (an alias of query) and a flat country.
Both paths return the same Envelope. The alias is just a shorter, sync-first shape for one surface.

Request

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 noise-cancelling headphones under $300",
    "surfaces": ["chatgpt"],
    "regions": [{ "country": "US" }]
  }'
The equivalent call via the alias — note prompt and flat country:
cURL
curl -X POST "https://api.aisearchapi.dev/v1/search/chatgpt" \
  -H "Authorization: Bearer $AISEARCH_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "prompt": "best noise-cancelling headphones under $300",
    "country": "US"
  }'

Response

You get 200 with the full Envelope for that one capture — the same structure a child returns from GET /v1/jobs/:id.
{
  "job": {
    "id": "job_8t2q.chatgpt.us",
    "status": "completed",
    "surface": "chatgpt",
    "credits": 5
  },
  "provenance": {
    "model": {
      "providerId": "openai",
      "observedLabel": "GPT-4o",
      "inferred": false,
      "confidence": 0.98
    },
    "region": { "requested": "US", "effective": "US" },
    "surfacePresent": true
  },
  "answer": {
    "text": "For under $300, the standouts are ...",
    "markdown": "For under $300, the standouts are ...",
    "blocks": [
      { "type": "paragraph", "text": "For under $300 ...", "referenceIds": ["s1"] }
    ]
  },
  "evidence": {
    "sources": [
      {
        "id": "s1",
        "url": "https://example.com/headphone-guide",
        "title": "2026 Headphone Buying Guide",
        "role": "citation",
        "cited": true,
        "charRanges": [[0, 42]],
        "quote": "our top pick under $300 ..."
      }
    ],
    "mentions": ["Sony", "Bose"],
    "shopping": [],
    "ads": []
  }
}
job
object
Identity for this capture: id (a dotted child id like job_8t2q.chatgpt.us), status, surface, and credits charged.
provenance
object
Detected model, region (requested vs effective), and surfacePresent. When surfacePresent is false and a surface_absent warning is present, the surface returned nothing — the call still completes with an empty answer.
answer
object
answer.markdown is always populated. answer.blocks[] carry referenceIds that point into evidence.sources.
evidence
object
Normalized sources, mentions, shopping, and ads. Every surface fills this same shape, so one parser handles all of them.
Read answer.markdown for display, then walk answer.blocks[].referenceIds back to evidence.sources[] to attach citations.

Errors specific to sync

Because the call blocks on a live capture, two error paths matter more here than in async.
Your key has a sync concurrency budget — the number of blocking captures allowed at once. When too many sync calls are already inflight, new ones get 429 CONCURRENCY_LIMIT_EXCEEDED. Wait for the Retry-After window, then retry. The response also carries X-Concurrency-Limit, X-Concurrency-Running, and X-Concurrency-Queued so you can back off precisely. See concurrency & rate limits.
If the surface doesn’t respond within the capture window, you get 504 SURFACE_TIMEOUT. This is a transient upstream timeout — retry the request. No credits are charged, since credits apply only on success.
All 429s also carry Retry-After plus X-RateLimit-Limit, X-RateLimit-Remaining, and X-RateLimit-Reset. Honor Retry-After before retrying.

When not to use sync

Do not use synchronous mode for fan-out. A sync call captures one surface × region and holds the connection for its entire duration — issuing many in parallel will exhaust your sync concurrency budget and stall behind slow captures. For multiple surfaces or regions, submit one async POST /v1/search and receive results via webhooks or by polling children. See asynchronous requests.

Next steps

Asynchronous requests

Fan out across surfaces and regions without blocking.

Concurrency & limits

Understand the sync budget, rate limits, and the headers to back off on.