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

# Ask a question from the website widget

> Ask a question in a widget thread and wait for the complete, cited answer. Takes the widget key, the visitor token, and an Origin the embed and platform allow.

Call this from the visitor's browser. It takes the embed's **public widget key** in `X-ARUKZ-Widget-Key`, and the browser's `Origin` must be allowed by both the deployment's `allowed_origins` and the platform-wide widget allowlist. See [Website widget](/channels/widget).

You must also send the thread's `X-ARUKZ-Visitor-Token`. The token is checked before the question is recorded or any model is called. A missing token, another thread's token and an unknown conversation all return the same `404`.

The answer is generated from the assistant's own knowledge, with citations. When the knowledge does not contain the answer, the assistant abstains: the response is still `201`, with `answer.abstained` set to `true` and no citations. See [How answers work](/concepts/how-answers-work).

A thread answers one question at a time. Sending a question while the previous answer is still being generated returns `409`. To retry safely after a dropped connection, send the same `idempotency_key`: if that question was already answered, you get the original turn back and the model is not called again. To show the answer as it is written, use [Stream a widget message](/api-reference/widget/stream-thread-message) instead.

When a request is refused, the response carries no `Access-Control-Allow-Origin` header, so in a cross-origin browser request `fetch` rejects with a network error instead of exposing the error body.

<ParamField path="conversation_id" type="string" required>
  The conversation's ID (UUID).
</ParamField>

<ParamField header="X-ARUKZ-Widget-Key" type="string" required>
  The deployment's public widget key, `arukz_wk_…`.
</ParamField>

<ParamField header="X-ARUKZ-Visitor-Token" type="string" required>
  The visitor token returned when this thread was started.
</ParamField>

<ParamField header="Origin" type="string" required>
  Set by the browser. It must exactly match an origin allowed for this deployment. When you call
  from outside a browser, set it yourself.
</ParamField>

<ParamField body="text" type="string" required>
  The question. Up to 16,000 characters, and not empty after surrounding whitespace is trimmed.
  Newlines and tabs are allowed; other control characters are refused.
</ParamField>

<ParamField body="idempotency_key" type="string">
  Your own key for this question, up to 128 characters of `A-Z`, `a-z`, `0-9`, `_`, `.`, `:` and
  `-`. Resending a key that was already answered in this thread returns that earlier turn instead of
  asking again.
</ParamField>

<ParamField body="metadata" type="object">
  Your own string labels for the question. At most 20 entries; keys are 1 to 64 characters of
  lowercase `a-z`, `0-9`, `_`, `.` and `-`, starting with a letter; values are up to 512 characters.
</ParamField>

## Response

Returns `201 Created` with the updated conversation, the question and the answer.

<ResponseField name="conversation" type="object" required>
  The conversation after this turn, with the same fields as [Get a widget
  conversation](/api-reference/widget/get-thread). If the thread was started without a title, its
  `title` is taken from the first line of the first question, shortened to 80 characters.
</ResponseField>

<ResponseField name="question" type="object" required>
  The visitor's message, with `role` set to `user`. Same fields as `answer`.
</ResponseField>

<ResponseField name="answer" type="object" required>
  The assistant's message.

  <Expandable title="properties">
    <ResponseField name="id" type="string" required>
      The message's ID (UUID).
    </ResponseField>

    <ResponseField name="conversation_id" type="string" required>
      The conversation it belongs to.
    </ResponseField>

    <ResponseField name="role" type="string" required>
      `assistant`.
    </ResponseField>

    <ResponseField name="sequence" type="integer" required>
      Position in the conversation.
    </ResponseField>

    <ResponseField name="status" type="string" required>
      `complete` for a finished answer.
    </ResponseField>

    <ResponseField name="text" type="string" required>
      The answer, with `[n]` markers that match `citations[].ordinal`.
    </ResponseField>

    <ResponseField name="citations" type="object[]" required>
      Sources the answer is grounded in. Empty when the assistant abstained.

      <Expandable title="properties">
        <ResponseField name="source_id" type="string">
          Identifier of the cited passage. It stays the same while the document's content is
          unchanged and changes when changed content is re-ingested. It is not a knowledge source ID
          or a document ID.
        </ResponseField>

        <ResponseField name="ordinal" type="integer">
          The citation's number.
        </ResponseField>

        <ResponseField name="title" type="string">
          Document title. May be an empty string.
        </ResponseField>

        <ResponseField name="url" type="string | null">
          Link to the source.
        </ResponseField>

        <ResponseField name="snippet" type="string | null">
          The passage the answer relied on.
        </ResponseField>

        <ResponseField name="anchor" type="string | null">
          Section anchor within the source.
        </ResponseField>

        <ResponseField name="score" type="number | null">
          Retrieval relevance score.
        </ResponseField>
      </Expandable>
    </ResponseField>

    <ResponseField name="abstained" type="boolean" required>
      `true` when the knowledge did not contain the answer.
    </ResponseField>

    <ResponseField name="created_at" type="string" required>
      ISO 8601 in UTC.
    </ResponseField>

    <ResponseField name="model" type="string | null" required>
      The model that produced the answer.
    </ResponseField>

    <ResponseField name="finish_reason" type="string | null" required>
      Why generation ended, for example `stop`, or `length` when the answer was truncated.
    </ResponseField>

    <ResponseField name="idempotency_key" type="string | null" required>
      Always `null` on an answer; the key is recorded on the question.
    </ResponseField>

    <ResponseField name="metadata" type="object" required>
      Always `{}` on an answer.
    </ResponseField>
  </Expandable>
</ResponseField>

<RequestExample>
  ```javascript JavaScript theme={"theme":{"light":"github-light","dark":"github-dark"}}
  // Runs on your website.
  const MITHUNAI_API = `${MITHUNAI_URL}/arukz/api/v1`
  const WIDGET_KEY = 'arukz_wk_8d0f5a2e-3c41-4b7a-9e6d-1f2a3b4c5d6e'
  const conversationId = sessionStorage.getItem('mithunai.conversation')

  const response = await fetch(`${MITHUNAI_API}/widget/conversations/${conversationId}/messages`, {
    method: 'POST',
    headers: {
      'X-ARUKZ-Widget-Key': WIDGET_KEY,
      'X-ARUKZ-Visitor-Token': sessionStorage.getItem('mithunai.visitorToken'),
      'Content-Type': 'application/json',
    },
    body: JSON.stringify({
      text: 'How do I raise my rate limit?',
      idempotency_key: crypto.randomUUID(),
    }),
  })
  const { answer } = await response.json()
  console.log(answer.text, answer.citations)
  ```

  ```bash cURL theme={"theme":{"light":"github-light","dark":"github-dark"}}
  curl --request POST "$MITHUNAI_URL/arukz/api/v1/widget/conversations/0e6a9b52-7d3f-4c18-a2e5-9b8c7d6e5f4a/messages" \
    --header "X-ARUKZ-Widget-Key: arukz_wk_8d0f5a2e-3c41-4b7a-9e6d-1f2a3b4c5d6e" \
    --header "X-ARUKZ-Visitor-Token: $VISITOR_TOKEN" \
    --header "Origin: https://docs.example.com" \
    --header "Content-Type: application/json" \
    --data '{"text": "How do I raise my rate limit?", "idempotency_key": "q-7f3c1e"}'
  ```
</RequestExample>

<ResponseExample>
  ```json 201 theme={"theme":{"light":"github-light","dark":"github-dark"}}
  {
    "conversation": {
      "id": "0e6a9b52-7d3f-4c18-a2e5-9b8c7d6e5f4a",
      "assistant_id": "5b1e9c2a-7d4f-4e3b-9a61-0c8f2d7e4a13",
      "title": "How do I raise my rate limit?",
      "status": "active",
      "turn_state": "idle",
      "message_count": 2,
      "created_at": "2026-09-24T10:02:17.559310+00:00",
      "updated_at": "2026-09-24T10:02:24.910447+00:00",
      "owner": { "type": "widget", "id": "8d0f5a2e-3c41-4b7a-9e6d-1f2a3b4c5d6e" },
      "metadata": { "page": "/guides/rate-limits" }
    },
    "question": {
      "id": "4a1b2c3d-5e6f-4a7b-8c9d-0e1f2a3b4c5d",
      "conversation_id": "0e6a9b52-7d3f-4c18-a2e5-9b8c7d6e5f4a",
      "role": "user",
      "sequence": 1,
      "status": "complete",
      "text": "How do I raise my rate limit?",
      "citations": [],
      "abstained": false,
      "created_at": "2026-09-24T10:02:21.004118+00:00",
      "model": null,
      "finish_reason": null,
      "idempotency_key": "q-7f3c1e",
      "metadata": {}
    },
    "answer": {
      "id": "9f8e7d6c-5b4a-4c3d-9e2f-1a0b9c8d7e6f",
      "conversation_id": "0e6a9b52-7d3f-4c18-a2e5-9b8c7d6e5f4a",
      "role": "assistant",
      "sequence": 2,
      "status": "complete",
      "text": "Rate limits are set per plan. To request a higher limit, open a support ticket from the billing page and include your expected peak requests per minute [1].",
      "citations": [
        {
          "source_id": "6e1d3c5b-9a7f-4b2e-8d0c-3f5a7b9d1e2f",
          "ordinal": 1,
          "title": "Rate limits",
          "url": "https://docs.example.com/guides/rate-limits",
          "snippet": "To request a higher limit, open a support ticket from the billing page.",
          "anchor": "requesting-a-higher-limit",
          "score": 0.82
        }
      ],
      "abstained": false,
      "created_at": "2026-09-24T10:02:21.004118+00:00",
      "model": "anthropic/claude-sonnet-5",
      "finish_reason": "stop",
      "idempotency_key": null,
      "metadata": {}
    }
  }
  ```

  ```json 400 theme={"theme":{"light":"github-light","dark":"github-dark"}}
  { "code": "validation_error", "message": "A message must contain text." }
  ```

  ```json 401 theme={"theme":{"light":"github-light","dark":"github-dark"}}
  { "code": "authentication_error", "message": "Authentication is required." }
  ```

  ```json 403 theme={"theme":{"light":"github-light","dark":"github-dark"}}
  { "code": "authorization_error", "message": "This widget may not be embedded from that origin." }
  ```

  ```json 404 theme={"theme":{"light":"github-light","dark":"github-dark"}}
  { "code": "not_found", "message": "The requested resource was not found." }
  ```

  ```json 409 theme={"theme":{"light":"github-light","dark":"github-dark"}}
  { "code": "conflict", "message": "This conversation is already waiting for an answer." }
  ```

  ```json 429 theme={"theme":{"light":"github-light","dark":"github-dark"}}
  { "code": "rate_limit_exceeded", "message": "Too many requests. Please retry later." }
  ```
</ResponseExample>
