> ## Documentation Index
> Fetch the complete documentation index at: https://docs.querying.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Migrating an existing integration

> Change two constants. Then read the four places behaviour differs.

This API implements the standard async task contract used by answer-engine APIs. For the
endpoints it supports, request and response schemas are unchanged, so a working client
usually needs no code edits.

## The migration

```diff theme={null}
- const BASE = "https://api.your-current-provider.example"
+ const BASE = "https://api.querying.ai"
```

Point your API-key constant at a querying.ai key. Your webhook URL, idempotency keys, task
types, and response parsing all stay as they are.

## What is implemented

<CardGroup cols={2}>
  <Card title="Supported" icon="check">
    `POST /v1/async/task`

    `POST /v1/async/task/batch`

    `GET /v1/async/task/{id}`

    Webhook callbacks
  </Card>

  <Card title="Not implemented" icon="x">
    `/v1/monitor/*` synchronous endpoints

    `/v1/async/status`
  </Card>
</CardGroup>

The synchronous `/v1/monitor/*` family is deliberately absent. Every engine here is driven
asynchronously; a request that blocks for the minutes a browser run can take is not a shape
this service offers.

## Where behaviour differs

<AccordionGroup>
  <Accordion title="1. Throughput varies sharply by engine" icon="gauge">
    The most important difference, and the only one that can bite you in production. Each
    engine's capacity here is the capacity of its backing pool of exit IPs, and we publish it
    rather than hiding it behind an opaque rate limiter.

    `GEMINI` sustains \~14 QPS. `PERPLEXITY` sustains ≤2. Browser-driven engines sustain much
    less. Over-submitting does not produce a 429 — it produces a growing queue and, eventually,
    `FAILED` tasks.

    See [Rate limits and capacity](/engines/capacity).
  </Accordion>

  <Accordion title="2. Credits are nominal, never billed" icon="coins">
    `credits` is present on every response as part of the envelope. Submit and poll responses
    report `{ "creditsToCharge": 0, "creditsCharged": 0 }`; webhooks report a nominal
    per-engine figure.

    Nothing is metered. If you aggregate credits for cost reporting, the numbers will not mean
    what they used to.
  </Accordion>

  <Accordion title="3. Some engine × region pairs are rejected outright" icon="ban">
    A few combinations cannot be served at all — `CHATGPT`/`PERPLEXITY`/`GEMINI` into `CN`,
    `GEMINI` into `DE`/`GB`/`IR`. Those fail immediately with `422 REGION_UNSUPPORTED` rather
    than being accepted. `422 REGION_UNAVAILABLE` covers the transient version of the same
    thing.

    A client that assumes every well-formed submission is accepted needs a branch here. See
    [Regions](/engines/capacity#regions).
  </Accordion>

  <Accordion title="4. include flags are mostly inert" icon="toggle-left">
    The `payload.include` flags are accepted, but most engines here return a fixed-shape
    response and ignore them. Only ChatGPT honours the full set; `GEMINI` honours `markdown`
    and `rawResponse` only.

    Requesting `html` from Gemini, for instance, is silently ignored — no error, no field.
    See the [flag support table](/engines/overview#the-include-flags-mostly-do-nothing).
  </Accordion>

  <Accordion title="5. One extra status code: 422" icon="hash">
    Error handling is otherwise conventional — validation failures return `400`, `401` reports
    `MISSING_API_KEY`, `404` reports `RESOURCE_NOT_FOUND`, and `details` names the offending
    field.

    The one addition is the `422` pair (`REGION_UNSUPPORTED`, `REGION_UNAVAILABLE`) described
    above. It exists because we run our own egress and tell you up front when a region cannot
    be served, so a client that has never had to handle a `422` will start seeing them.
  </Accordion>

  <Accordion title="6. A failed task adds a top-level `error` string" icon="triangle-alert">
    `GET /v1/async/task/{id}` on a `FAILED` task returns a top-level `error` field that the
    task-status schema does not otherwise define — and it is a **plain string**, not the
    `{code, message, timestamp}` object used by request-level errors.

    ```json theme={null}
    { "success": true, "task": { "status": "FAILED", "...": "..." }, "error": "engine timeout after 3 attempts" }
    ```

    A client that treats a truthy `body.error` as a failed *request* will misread a
    successfully-retrieved failed *task*. Branch on `task.status` instead.
  </Accordion>
</AccordionGroup>

<Note>
  These behaviours were established by calling this API, not by reading a schema. Where a
  published spec and a live response ever disagree, the live response is what these pages
  document.
</Note>

## Picking the right `taskType`

If your current integration calls a per-engine endpoint, the engine name maps onto a
`taskType`:

| You were calling   | `taskType`                         |
| ------------------ | ---------------------------------- |
| ChatGPT            | `CHATGPT`                          |
| Perplexity         | `PERPLEXITY`                       |
| Gemini             | `GEMINI`                           |
| Google AI Mode     | `AIMODE`                           |
| Google AI Overview | `GOOGLE`                           |
| Naver              | `NAVER_AI_BRIEF` / `NAVER_AI_MODE` |

<Note>
  `GOOGLE` meaning "AI Overview" is a naming convention, not a Google product name. The
  response is a SERP envelope with `aioverview` as a nullable member — see
  [the GOOGLE shape](/engines/overview#response-shapes).
</Note>

## Verifying the cutover

Run your existing integration and this one in parallel for a window, and diff the fields
you actually consume — in practice `response.text`, `response.sources[]`, and
`response.markdown`. A shadow
comparison catches shape drift that a schema check will not, such as a source list that is
populated but ordered differently.
