> ## 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 from Cloro

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

This API implements Cloro's async task contract. For the endpoints it supports, request
and response schemas are identical.

## The migration

```diff theme={null}
- const BASE = "https://api.cloro.dev"
+ const BASE = "https://api.querying.ai"
```

Point `CLORO_API_KEY` at your anymorph 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. Cloro
    absorbs your submission rate behind its own infrastructure. Here, each engine's capacity is
    the capacity of its backing pool.

    `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 because Cloro clients read it. Submit and poll
    responses report `{ "creditsToCharge": 0, "creditsCharged": 0 }`; webhooks report a
    per-engine figure mirroring Cloro's pricing table.

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

  <Accordion title="3. Two engines can reject a submission outright" icon="ban">
    `CHATGPT` and `CLAUDE` are region-gated. If no account can serve `payload.country`, the
    submission fails immediately with `422 REGION_UNAVAILABLE` rather than being accepted.

    Cloro has no equivalent. A client that assumes every well-formed submission is accepted
    needs a branch here.
  </Accordion>

  <Accordion title="4. include flags are mostly inert" icon="toggle-left">
    Cloro's `payload.include` flags are accepted, but most engines here return a fixed-shape
    response and ignore them. Only ChatGPT's anonymous mode honours the full set; several
    engines honour `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 REGION_UNAVAILABLE" icon="hash">
    Error handling is otherwise identical to the live Cloro API — same statuses, same
    `error.code` strings, same `details` shape. Validation failures return `400`, `401` reports
    `MISSING_API_KEY`, `404` reports `RESOURCE_NOT_FOUND`.

    The one addition is `422 REGION_UNAVAILABLE`, described above. Cloro has no account pools
    and therefore no equivalent, so a client that has never seen a `422` from Cloro will start
    seeing them here for `CHATGPT` and `CLAUDE`.
  </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 Cloro's
    `TaskStatusResponse` does not 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 differences were established by calling `api.cloro.dev` directly on 2026-07-09, not
  by reading its spec. Cloro's published OpenAPI disagrees with its own API on the validation
  status code (`422` documented, `400` returned) and on the shape of the validation error
  body (a string, versus the object the API actually sends). Where the two conflict, this
  API follows the live response.
</Note>

## Engine name mapping

Cloro's synchronous paths map onto `taskType` values like this:

| Cloro path               | `taskType`   |
| ------------------------ | ------------ |
| `/v1/monitor/chatgpt`    | `CHATGPT`    |
| `/v1/monitor/perplexity` | `PERPLEXITY` |
| `/v1/monitor/gemini`     | `GEMINI`     |
| `/v1/monitor/copilot`    | `COPILOT`    |
| `/v1/monitor/grok`       | `GROK`       |
| `/v1/monitor/aimode`     | `AIMODE`     |
| `/v1/monitor/aioverview` | `GOOGLE`     |

<Note>
  `GOOGLE` requesting AI Overview is a convention inherited from the original client, not a
  Cloro `taskType` in its own right. The response is a SERP envelope with `aioverview` as a
  nullable member — see [the GOOGLE shape](/engines/overview#response-shapes).
</Note>

Engines with no Cloro equivalent: `CLAUDE`, `DEEPSEEK`, `AMAZON`, `NAVER_AI_BRIEF`,
`NAVER_AI_MODE`.

## Verifying the cutover

Run both APIs 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.
