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

# Chat Query (Async)

> Submit a question and get the answer later via polling or a webhook callback.

Use `POST /chat/query-async/` when you don't want to hold an HTTP connection open while the agent works. It accepts the same body as [`POST /chat/query/`](/api-reference/knowledge-agent/chat-query) plus an optional `callback_url`, and returns `202 Accepted` **immediately** with a `conversation_id` — the agent keeps running in the background.

Because the response comes back before the answer is ready, you **must** pick one of two ways to collect the result:

1. **Poll** [`GET /chat/conversation/{conversation_id}/`](/api-reference/knowledge-agent/chat-conversation) until it reports `Completed`.
2. **Callback** — pass a `callback_url` and Voltai POSTs the finished result to it.

Choose one. If you provide a `callback_url` you don't need to poll; if you don't, polling is the only way to get the answer.

## Request

Send `X-API-KEY` in the header. The body must include `query` and `user_id`.

* **`query`** (required) — the user's question.
* **`user_id`** (required) — your identifier for the end-user. Each `(API key, user_id)` pair allows **one concurrent request**; a second in-flight request returns `429`.
* **`callback_url`** (optional) — an `http(s)` URL. When set, the final result is POSTed here as JSON once the run finishes. An invalid URL returns `400`.
* **`chat_id`** (optional) — UUID of a thread to continue. Omit to start a new thread.
* **`reasoning_level`** (optional) — `low`, `medium` (default), or `high`.
* **`topic_names`** (optional) — list of knowledge topic names to scope retrieval to.

## Response (202 Accepted)

The `202` body confirms the turn was accepted; it does **not** contain the answer.

```json theme={"dark"}
{
  "chat_id": "b0331c9f-b5e2-4062-92d3-d701bb0b856d",
  "conversation_id": 732538,
  "chat_url": "https://voltai.ai/ExampleOrg/chat/b0331c9f-b5e2-4062-92d3-d701bb0b856d",
  "status": "processing"
}
```

Hold on to `conversation_id` — you need it to poll for or correlate the result.

## Example Request

```bash theme={"dark"}
curl -X POST "https://api-prod.voltai.ai/chat/query-async/" \
  -H "Content-Type: application/json" \
  -H "X-API-KEY: $API_KEY" \
  -d '{"query": "What is EMEM?", "user_id": "usr_82af91"}'
```

## Option A — Poll for the result

If you did not pass a `callback_url`, poll [`GET /chat/conversation/{conversation_id}/`](/api-reference/knowledge-agent/chat-conversation) using the `conversation_id` from the `202` response. Check the `status` field on each response:

* **`Processing`** — the agent is still working; wait a moment and poll again.
* **`Completed`** — the answer is ready; the body carries the full `POST /chat/query/` payload (`final_answer`, `sources`, `tools`, etc.).
* **`Failed`** — the turn stopped without producing an answer.

```bash theme={"dark"}
curl -s "https://api-prod.voltai.ai/chat/conversation/732538/" \
  -H "X-API-KEY: $API_KEY"
```

A reasonable loop polls every few seconds until `status` is no longer `Processing`. This endpoint is read-only and does not consume session capacity.

## Option B — Receive a callback

If you pass a `callback_url`, you don't need to poll. When the run finishes, Voltai sends a `POST` to that URL with the same JSON body that `POST /chat/query/` returns (`final_answer`, `chat_id`, `chat_url`, `conversation_id`, `sources`, `tools`, `content`).

```bash theme={"dark"}
curl -X POST "https://api-prod.voltai.ai/chat/query-async/" \
  -H "Content-Type: application/json" \
  -H "X-API-KEY: $API_KEY" \
  -d '{
    "query": "What is EMEM?",
    "user_id": "usr_82af91",
    "callback_url": "https://your-app.example.com/voltai/webhook"
  }'
```

Your webhook endpoint should:

* Accept a `POST` with a JSON body and return quickly (a `2xx`).
* Use `conversation_id` to match the callback back to the original request.
* Handle the failure shape — if the run fails, the callback body is `{"chat_id", "conversation_id", "status": "error", "error"}` instead of the full answer.

Callback delivery is best-effort. If your endpoint is unreachable the result is still saved, so you can always fall back to `GET /chat/conversation/{conversation_id}/`.

## Errors

* **400** — missing `query`/`user_id`, malformed `chat_id`, unknown `topic_names`, or an invalid `callback_url`.
* **403** — invalid or missing API key, the key lacks the `chat` scope, or the org message limit is reached.
* **409** — `chat_id` was supplied but the UUID is already in use by a chat the API key cannot resume (a different organization, or a soft-deleted chat). Pick a different UUID, or omit `chat_id` to let the server mint one.
* **429** — the same `user_id` already has an in-flight request, or the org-wide session cap is reached. Check usage with [`GET /chat/sessions/`](/api-reference/knowledge-agent/chat-sessions).


## OpenAPI

````yaml chat-query.json POST /chat/query-async/
openapi: 3.1.0
info:
  title: Voltai API Docs
  description: .
  license:
    name: MIT
    url: https://opensource.org/licenses/MIT
  version: 1.0.0
servers:
  - url: https://api-prod.voltai.ai
security:
  - ApiKeyAuth: []
paths:
  /chat/query-async/:
    post:
      description: >-
        Fire-and-forget variant of `POST /chat/query/`. Accepts the same body
        plus an optional `callback_url`, validates the request, creates the chat
        turn, and returns `202` with the identifiers **immediately** while the
        agent runs in the background.


        Retrieve the answer in one of two ways: poll [`GET
        /chat/conversation/{conversation_id}/`](/api-reference/knowledge-agent/chat-conversation)
        until `status` is `Completed`, or supply a `callback_url` and receive
        the result via webhook when the run finishes.
      requestBody:
        description: Same payload as `POST /chat/query/`, plus an optional `callback_url`.
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/ChatQueryAsync'
        required: true
      responses:
        '202':
          description: >-
            The request was accepted and the agent is running in the background.
            Use `conversation_id` to poll `GET
            /chat/conversation/{conversation_id}/`, or wait for the
            `callback_url` webhook if one was supplied.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ChatQueryAsyncAccepted'
        '400':
          description: >-
            Invalid request (missing `query`/`user_id`, malformed `chat_id`,
            unknown `topic_names`, or a `callback_url` that is not a valid
            http(s) URL).
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '403':
          description: >-
            Invalid or missing API key, the key lacks the `chat` scope, or the
            organization has reached its message limit.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '409':
          description: >-
            `chat_id` was supplied but the UUID is already in use by a chat the
            API key cannot resume (a different organization, or a soft-deleted
            chat). Pick a different UUID, or omit `chat_id` to let the server
            mint one.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '429':
          description: >-
            Concurrent session limit exceeded: either the same `user_id` already
            has an in-flight request (`user_session_limit_exceeded`), or the
            organization-wide seat cap is reached (`session_limit_exceeded`).
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SessionLimitError'
components:
  schemas:
    ChatQueryAsync:
      description: >-
        Request body for `POST /chat/query-async/`. Identical to `ChatQuery`
        plus an optional `callback_url` for webhook delivery of the result.
      allOf:
        - $ref: '#/components/schemas/ChatQuery'
        - type: object
          properties:
            callback_url:
              type: string
              format: uri
              description: >-
                Optional http(s) URL. When provided, the final result is POSTed
                to this URL as JSON once the agent finishes. Must be a valid
                http(s) URL or the request returns 400. Omit it if you plan to
                poll `GET /chat/conversation/{conversation_id}/` instead.
    ChatQueryAsyncAccepted:
      description: >-
        Acknowledgement returned immediately (`202`) by `POST
        /chat/query-async/`. The answer is not included — fetch it later via
        `GET /chat/conversation/{conversation_id}/` or receive it on your
        `callback_url`.
      type: object
      required:
        - chat_id
        - conversation_id
        - chat_url
        - status
      properties:
        chat_id:
          type: string
          format: uuid
          description: >-
            Thread identifier for this conversation. Reuse it (as `chat_id`) in
            later requests to continue the same thread.
        conversation_id:
          type: integer
          description: >-
            Identifier of the turn just created. Poll `GET
            /chat/conversation/{conversation_id}/` with this value to fetch the
            answer once it is ready.
        chat_url:
          type: string
          format: uri
          description: Web UI link to this thread.
        status:
          type: string
          enum:
            - processing
          description: >-
            Always `processing`: the turn was accepted and the agent is running
            in the background.
    Error:
      type: object
      properties:
        detail:
          type: string
          description: Human-readable error description.
    SessionLimitError:
      type: object
      description: Returned when a concurrent session limit is exceeded.
      required:
        - error
        - message
      properties:
        error:
          type: string
          description: >-
            Error code. `user_session_limit_exceeded` when the same `user_id`
            already has an in-flight request. `session_limit_exceeded` when the
            organization-wide session cap is reached.
          enum:
            - user_session_limit_exceeded
            - session_limit_exceeded
        message:
          type: string
          description: Human-readable error description.
        user_id:
          type: string
          description: >-
            The `user_id` that triggered the per-user limit. Present only for
            `user_session_limit_exceeded`.
        total_seats:
          type: integer
          description: >-
            Total allowed concurrent sessions for the organization. Present only
            for `session_limit_exceeded`.
        active_seats:
          type: integer
          description: >-
            Currently active sessions. Present only for
            `session_limit_exceeded`.
        available_seats:
          type: integer
          description: >-
            Remaining available sessions. Present only for
            `session_limit_exceeded`.
    ChatQuery:
      required:
        - query
        - user_id
      type: object
      description: >-
        Provide `query` and `user_id`. Topic scope is chosen automatically
        unless you pass `topic_names`. The agent decides its own reasoning depth
        — use `reasoning_level` to bias it.
      properties:
        query:
          description: The user's question.
          type: string
        user_id:
          description: >-
            Your application's identifier for the end-user making the request.
            Used for per-user session tracking and concurrency enforcement. Each
            `(API key, user_id)` pair is limited to one concurrent request.
          type: string
          maxLength: 255
        chat_id:
          description: >-
            Existing chat thread to continue. Must be a valid UUID. If it
            matches a chat in your organization, the new turn is appended; if
            the UUID is well-formed but not yet used, the server creates a new
            chat under that UUID (useful for client-side idempotency /
            correlation). Omit to let the server mint a fresh UUID. A UUID that
            already belongs to a chat the API key cannot resume returns `409`.
          type: string
          format: uuid
        reasoning_level:
          description: >-
            Bias for how much intermediate reasoning the agent should do. `low`
            favors quick answers, `high` favors thorough multi-step reasoning.
            Unknown values fall back to `medium`.
          type: string
          enum:
            - low
            - medium
            - high
          default: medium
        topic_names:
          description: >-
            Optional. Names of knowledge topics to scope retrieval to. Each must
            match a topic in your organization (case-insensitive). If omitted or
            empty, topic scope is chosen automatically. If any name does not
            match a topic, the request returns 400.
          type: array
          items:
            type: string
        topic_name:
          description: >-
            Deprecated singular alias for `topic_names`. When `topic_names` is
            omitted, `topic_name` (if present) is wrapped into a one-element
            list. Prefer `topic_names` for new integrations.
          type: string
          deprecated: true
  securitySchemes:
    ApiKeyAuth:
      type: apiKey
      in: header
      name: x-api-key

````