---
title: "Ask an assistant a question"
description: "Ask an assistant a question and get an answer written from its resources, with the passages it relied on. Spends credits."
canonical: "https://lookio.app/docs/api/assistants/query-assistant"
updated: "2026-10-25"
---

# Ask an assistant a question

Requires `query`.

This is the only call that spends credits. The cost depends on `mode`:

| `mode`            | Credits |
| ----------------- | ------- |
| `eco`             | 10      |
| `flash` (default) | 30      |
| `deep`            | 80      |

The price is fixed, whatever the length of the answer. An answer that finds nothing, or a greeting, costs the same: every question the model works on is charged. The only refusal that costs nothing is `assistant_has_no_resources` (409): the assistant reads no resource, so nothing is sent to a model and no credit is held. Connect resources to the assistant, then ask again. See [Core concepts](/docs/get-started/concepts#query-modes).

Lookio holds the price of the mode before it starts. If the balance cannot cover the hold, the call fails with `insufficient_credits`, and the error carries a link to the pricing page in `action_url`.

**Conversations.** Every answer is kept in a conversation, for a person and for an API key. The response carries `conversation_id` and, for an API key, the answer's `message_id`. Send the next question with that `conversation_id` and Lookio reads the history itself. A conversation stays with its assistant and, for an API key, with the key that started it. Sending `conversation_id` with `history` fails with `invalid_request`: `history` is for when you keep the conversation yourself (up to 20 turns, the last 6 are used).

**End users and storage (API keys).** Send `end_user`, your own id for the person asking, to group that person's conversations: a conversation can then only be continued, read or deleted with the same `end_user`. Send `store: false` to keep nothing: no conversation and no log, and `conversation_id` and `message_id` come back `null`. A kept conversation is readable in the workspace's [logs](/docs/platform/logs) and deleted 90 days after its last message. A person's connection that sends `end_user` or `store` gets `invalid_parameter`. See [Conversations and end users](/docs/platform/conversations-and-end-users).

**Streaming.** Set `stream` to `true` to receive server-sent events instead of JSON. See [Streaming](/docs/api/streaming).

**Retries.** Send an `Idempotency-Key` so a retried JSON request is not charged twice. A stream is not replayed: do not retry one blindly. See [Idempotency](/docs/api/conventions#idempotency).

The assistant answers only from resources that are `ready`. When the sources hold no answer, it says so and charges the same price for that question.

```http
POST https://api.lookio.app/v1/assistants/{assistant_id}/query
```

## Authorizations

- `Authorization` (bearer, in header, required)

## Path parameters

- `assistant_id` (`string`, required, pattern: `^ast_[abcdefghjkmnpqrstuvwxyz23456789]{12}$`)

## Header parameters

- `Idempotency-Key` (`string`, length: `1–255 chars`): A unique key for this request, such as a UUID, 1 to 255 visible ASCII characters. Send the same key when you retry: the first response is returned (with `Idempotent-Replayed: true`) instead of running the request again, for 24 hours, per credential. The same key on a different request is a 409 `conflict`. A streamed answer is not recorded.

## Body

`application/json`, required

- `question` (`string`, required, length: `1–12000 chars`)
- `conversation_id` (`string`, pattern: `^(?:res|doc|ver|ast|wdg|cnv|msg|att|acc|aky)_[abcdefghjkmnpqrstuvwxyz23456789]{12}$`): Continue a conversation from an earlier answer's `conversation_id`: Lookio reads its history itself, so send only the new question. A person continues their own chats; an API key continues the conversations it started (and, when you send `end_user`, only that person's). A conversation stays with the assistant that started it. Not with `history` and not with `store: false`.
- `history` (`object[]`, items: `≤ 20 items`): The previous turns, oldest first; the last 6 are used, and they are not stored as such. For a conversation you keep yourself. Without `conversation_id`, an API key's answer starts a new conversation in Lookio's logs unless `store` is false.
- `history.role` (`enum<string>`, required, one of: `user, assistant`)
- `history.content` (`string`, required, length: `≤ 12000 chars`)
- `end_user` (`string`, pattern: `^[\w.@:+\-]{1,200}$`): API keys only. Your own id for the person asking (a user id of your product): 1 to 200 characters, letters, digits and `. _ @ : + -`. Prefer an opaque id: it shows in the workspace's logs. It groups that person's conversations, and a conversation can then only be continued with the same `end_user`.
- `store` (`boolean`): API keys only. `false` keeps nothing: no conversation, no log, `conversation_id` and `message_id` come back null, and the answer cannot be continued except with `history`. Default `true`: the exchange is kept, continues with `conversation_id` and is readable in the workspace's logs for as long as the plan keeps conversations readable (7 days on Free, 90 on Starter, 1 year on Pro). Not with `conversation_id`.
- `mode` (`enum<string>`, default: `flash`, one of: `eco, flash, deep`): Eco 10, Flash 30 credits (the default), Deep 80. flash is the default; eco may take longer to start, deep splits the question and answers with a stronger model. One price per answer, whatever its length. Every question the assistant works on is charged, even when its resources hold no answer or the message is a greeting; an assistant that reads no resource is refused with the error assistant_has_no_resources, before any credit is held.
- `stream` (`boolean`, default: `false`): `true` returns server-sent events (`text/event-stream`): `status` (searching, reading, answering), `delta` with each piece of the answer, then `done` with the full answer, or `error` with the standard error envelope. Comment lines (`: keep-alive`) arrive every 15 seconds while waiting. Idempotency-Key does not apply to a stream.

## Response

**200**: Ask an assistant a question

- `data` (`object`, required)
- `data.answer` (`string`, required): Markdown. Citations appear as [1], [2]…
- `data.citations` (`object[]`, required)
- `data.citations.resource_id` (`string`, required): The cited resource. Empty for a public assistant of another workspace: its resources stay its own.
- `data.citations.title` (`string`, required)
- `data.citations.text` (`string`, required): The passage the answer relies on.
- `data.citations.n` (`integer`): The number the answer cites it by, as [1].
- `data.citations.section` (`string[]`)
- `data.citations.page` (`integer`)
- `data.citations.url` (`string`)
- `data.citations.source` (`string`): The cited resource's source, when it has one.
- `data.conversation_id` (`string,null`, required): The conversation this answer belongs to: pass it back as `conversation_id` to continue it. Null when nothing was stored (`store: false`).
- `data.message_id` (`string,null`, required): The id of this answer in the conversation. On an API key's conversation it is what `POST /v1/conversations/{conversation_id}/messages/{message_id}/feedback` takes (a person's own chats take no thumbs yet). Null when nothing was stored.
- `data.credit_cost` (`integer`, required): Credits this answer cost. A model answer costs its mode's fixed price.
- `data.credits_remaining` (`integer | null`, required)
- `data.simulated` (`boolean`, required): True on a development deployment answering without a model.
- `request_id` (`string`, required)

```json
{
  "data": {
    "answer": "You can return an item within 30 days of delivery [1].",
    "citations": [
      {
        "resource_id": "res_thk6smb5nvts",
        "title": "Refund policy",
        "text": "Items can be returned within 30 days of delivery, unused and in their original packaging.",
        "n": 1,
        "section": [
          "Refunds",
          "Window"
        ],
        "source": "Employee handbook, 2026 edition"
      }
    ],
    "conversation_id": "cnv_ca9xz837zsvb",
    "message_id": "msg_6ec9htp67dd7",
    "credit_cost": 30,
    "credits_remaining": 9970,
    "simulated": false
  },
  "request_id": "req_01K9X7M2Q4"
}
```

**400**: Possible codes: invalid_request, unknown_field, invalid_parameter, invalid_cursor

- `error` (`object`, required)
- `error.type` (`string`, required)
- `error.code` (`string`, required)
- `error.message` (`string`, required)
- `error.param` (`string`)
- `error.docs_url` (`string`, required)
- `error.action_url` (`string<uri>`)
- `request_id` (`string`, required)

```json
{
  "error": {
    "type": "<string>",
    "code": "<string>",
    "message": "<string>",
    "param": "<string>",
    "docs_url": "<string>",
    "action_url": "<string>"
  },
  "request_id": "<string>"
}
```

**401**: Possible codes: authentication_required, invalid_api_key, api_key_expired, api_key_revoked, invalid_access_key

- `error` (`object`, required)
- `error.type` (`string`, required)
- `error.code` (`string`, required)
- `error.message` (`string`, required)
- `error.param` (`string`)
- `error.docs_url` (`string`, required)
- `error.action_url` (`string<uri>`)
- `request_id` (`string`, required)

```json
{
  "error": {
    "type": "<string>",
    "code": "<string>",
    "message": "<string>",
    "param": "<string>",
    "docs_url": "<string>",
    "action_url": "<string>"
  },
  "request_id": "<string>"
}
```

**402**: Possible codes: insufficient_credits

- `error` (`object`, required)
- `error.type` (`string`, required)
- `error.code` (`string`, required)
- `error.message` (`string`, required)
- `error.param` (`string`)
- `error.docs_url` (`string`, required)
- `error.action_url` (`string<uri>`)
- `request_id` (`string`, required)

```json
{
  "error": {
    "type": "<string>",
    "code": "<string>",
    "message": "<string>",
    "param": "<string>",
    "docs_url": "<string>",
    "action_url": "<string>"
  },
  "request_id": "<string>"
}
```

**403**: Possible codes: insufficient_scope, feature_not_available, admin_required, permission_denied

- `error` (`object`, required)
- `error.type` (`string`, required)
- `error.code` (`string`, required)
- `error.message` (`string`, required)
- `error.param` (`string`)
- `error.docs_url` (`string`, required)
- `error.action_url` (`string<uri>`)
- `request_id` (`string`, required)

```json
{
  "error": {
    "type": "<string>",
    "code": "<string>",
    "message": "<string>",
    "param": "<string>",
    "docs_url": "<string>",
    "action_url": "<string>"
  },
  "request_id": "<string>"
}
```

**404**: Possible codes: resource_not_found

- `error` (`object`, required)
- `error.type` (`string`, required)
- `error.code` (`string`, required)
- `error.message` (`string`, required)
- `error.param` (`string`)
- `error.docs_url` (`string`, required)
- `error.action_url` (`string<uri>`)
- `request_id` (`string`, required)

```json
{
  "error": {
    "type": "<string>",
    "code": "<string>",
    "message": "<string>",
    "param": "<string>",
    "docs_url": "<string>",
    "action_url": "<string>"
  },
  "request_id": "<string>"
}
```

**409**: Possible codes: conflict, assistant_has_no_resources

- `error` (`object`, required)
- `error.type` (`string`, required)
- `error.code` (`string`, required)
- `error.message` (`string`, required)
- `error.param` (`string`)
- `error.docs_url` (`string`, required)
- `error.action_url` (`string<uri>`)
- `request_id` (`string`, required)

```json
{
  "error": {
    "type": "<string>",
    "code": "<string>",
    "message": "<string>",
    "param": "<string>",
    "docs_url": "<string>",
    "action_url": "<string>"
  },
  "request_id": "<string>"
}
```

**413**: Possible codes: request_too_large

- `error` (`object`, required)
- `error.type` (`string`, required)
- `error.code` (`string`, required)
- `error.message` (`string`, required)
- `error.param` (`string`)
- `error.docs_url` (`string`, required)
- `error.action_url` (`string<uri>`)
- `request_id` (`string`, required)

```json
{
  "error": {
    "type": "<string>",
    "code": "<string>",
    "message": "<string>",
    "param": "<string>",
    "docs_url": "<string>",
    "action_url": "<string>"
  },
  "request_id": "<string>"
}
```

**429**: Possible codes: rate_limited

- `error` (`object`, required)
- `error.type` (`string`, required)
- `error.code` (`string`, required)
- `error.message` (`string`, required)
- `error.param` (`string`)
- `error.docs_url` (`string`, required)
- `error.action_url` (`string<uri>`)
- `request_id` (`string`, required)

```json
{
  "error": {
    "type": "<string>",
    "code": "<string>",
    "message": "<string>",
    "param": "<string>",
    "docs_url": "<string>",
    "action_url": "<string>"
  },
  "request_id": "<string>"
}
```

**500**: Possible codes: internal_error

- `error` (`object`, required)
- `error.type` (`string`, required)
- `error.code` (`string`, required)
- `error.message` (`string`, required)
- `error.param` (`string`)
- `error.docs_url` (`string`, required)
- `error.action_url` (`string<uri>`)
- `request_id` (`string`, required)

```json
{
  "error": {
    "type": "<string>",
    "code": "<string>",
    "message": "<string>",
    "param": "<string>",
    "docs_url": "<string>",
    "action_url": "<string>"
  },
  "request_id": "<string>"
}
```

**503**: Possible codes: service_unavailable

- `error` (`object`, required)
- `error.type` (`string`, required)
- `error.code` (`string`, required)
- `error.message` (`string`, required)
- `error.param` (`string`)
- `error.docs_url` (`string`, required)
- `error.action_url` (`string<uri>`)
- `request_id` (`string`, required)

```json
{
  "error": {
    "type": "<string>",
    "code": "<string>",
    "message": "<string>",
    "param": "<string>",
    "docs_url": "<string>",
    "action_url": "<string>"
  },
  "request_id": "<string>"
}
```

**Example request**

```bash
curl --request POST \
  --url 'https://api.lookio.app/v1/assistants/<assistant_id>/query' \
  --header 'Authorization: <api-key>' \
  --header 'Content-Type: application/json' \
  --data '{
  "question": "<string>"
}'
```
