---
title: "Ask Dardaris a question about the portfolio"
method: POST
path: "/api/v1/ask"
tags: ["Dardaris"]
---

# Ask Dardaris a question about the portfolio

`POST /api/v1/ask`

## Request

- Base URL: `http://localhost:5180`
- URL: `http://localhost:5180/api/v1/ask`
- Auth: API key in cookie `better-auth.session_token`

Every question goes to the assistant (`status: generated`), which decides how to answer it: it works figures out exactly with the answering engine over the records and shows them as `cards`, reads the filing and the help pages, reads the portfolio through the MCP actions, and proposes any change as `actions` rather than making it. Where no model can answer, the engine answers from the records on its own, and a question asking for a document is put to the filing, whose match is the passage the document actually contains. When nothing can be said, `answer` is null and `followUps` becomes a handful of questions that are known to have answers.

The turn is read against the last one: send it back as `previous` and a follow-up carrying only a qualifier keeps the topic it inherits, so "and per building?" is read against the answer above it.

Nothing is stored unless you say where to store it. Name a `conversationId` and the turn is appended to that transcript, which is what the front desk does so a chat can be read again; leave it out and the request is stateless. Name a `turnId` as well and asking the same turn again, after an answer that was lost on the way back, writes it once.

## Request body

- object
  - `question` string, required: Plain language, e.g. "which units are empty?". At most 2000 characters.
  - `previous` object: The last turn that was answered. Send it and a bare qualifier such as "and per building?" is read against that answer instead of falling through as unanswerable. Leave it out and the question stands on its own.
    - `key` string, required: The `key` of the answer that turn produced.
    - `question` string, required: The question that produced it, word for word.
  - `conversationId` string: The conversation to write this turn into, minted by the client the way a saved view’s id is. The row is created on the first turn that names it. Leave it out and the question is answered and not written down, which is what a script or an assistant should do.
  - `history` object[]: The turns above this one as text, oldest first. Read only when the assistant writes the answer.
    - `question` string, required
    - `answer` string, required
    - `context` string: The `context` that answer came with: what it looked up.
    - `attachments` object[]: The files that question carried, so a later turn can still file one by its id.
      - `id` string, required: The `id` `POST /ask/attachments` answered with.
      - `name` string, required
  - `attachments` string[]: Files attached with `POST /ask/attachments`, by id. The assistant reads them with the question, and can propose filing one.
  - `turnId` string: The client’s own id for this turn. Ask again with the same id after an answer that never arrived and the transcript keeps the turn once. Only read alongside a `conversationId`.

## Response `200`

The answer, or null with suggestions. Sent with `Accept: application/x-ndjson`, each step the assistant takes arrives as a line while it works, and the envelope is the last line.

- object
  - `ok` unknown, required
  - `data` object, required
    - `question` string, required: The question as it was understood.
    - `answer` object, nullable, required: Null when nothing on file answers it, never a guess. `explanation` and `sourceCount` say what the figure was computed from.
      - `key` string, required: Stable id of the builder that produced it, so a pinned view can recompute the answer from live records.
      - `title` string, required
      - `status` 'unsure' | 'failed' | 'expired' | 'chat' | 'generated': Why the answer reads the way it does. Absent is the ordinary case: the question matched squarely and the figure was computed from records. `unsure` matched only just, and the explanation leads with that doubt. `failed` means the builder threw, so nothing was computed and no figure is claimed. `expired` is a pinned view whose builder has left the library. `chat` is a greeting or a thank you, not a figure at all. A client must not render `failed` or `chat` as a measurement.
      - `headline` object
        - `value` string, required
        - `label` string, required
        - `tone` 'neutral' | 'brand' | 'good' | 'warning' | 'serious' | 'critical' | 'info'
      - `explanation` string, required: What was computed, and from which records. This is what makes the figure checkable.
      - `table` object
        - `columns` string[], required
        - `rows` array[], required
          - union[]
            - union
              - …
        - `align` string[]
      - `chart` object
        - `kind` 'bar' | 'line' | 'donut', required
        - `labels` string[], required
        - `series` object[], required
          - `key` string, required
          - `label` string, required
          - `values` number[], required
        - `format` 'money' | 'percent' | 'number'
      - `list` object[]
        - `label` string, required
        - `sublabel` string
        - `value` string
        - `tone` 'neutral' | 'brand' | 'good' | 'warning' | 'serious' | 'critical' | 'info'
        - `link` string
        - `quote` string: A document’s own words where it matched the question.
        - `documentId` string: The document this row is, for a filing answer.
      - `links` object[]: Where to go for the full detail.
        - `label` string, required
        - `to` string, required
      - `sourceCount` number, required: How many records the figure was derived from. This is what makes it trustworthy.
      - `sourceLabel` string, required
      - `action` object: The first of `actions`, kept for a reader built before an answer could propose more than one.
        - `action` string, required: The operationId to run, as the MCP catalogue names it.
        - `summary` string, required: What running it does, in one sentence.
        - `path` object
        - `query` object
        - `body` unknown
      - `actions` object[]: Every change the assistant worked out and did not make, in the order it means them to be made. Nothing happens until each is sent to `POST /ask/actions`.
        - `action` string, required: The operationId to run, as the MCP catalogue names it.
        - `summary` string, required: What running it does, in one sentence.
        - `path` object
        - `query` object
        - `body` unknown
      - `handoff` boolean: True when the person asked for a human, so the chat is for the helpdesk.
      - `cards` object[]: Answers worked out from the records that a written answer shows under itself, each with its own figure, table or chart.
        - `key` string, required: Stable id of the builder that produced it, so a pinned view can recompute the answer from live records.
        - `title` string, required
        - `status` 'unsure' | 'failed' | 'expired' | 'chat' | 'generated': Why the answer reads the way it does. Absent is the ordinary case: the question matched squarely and the figure was computed from records. `unsure` matched only just, and the explanation leads with that doubt. `failed` means the builder threw, so nothing was computed and no figure is claimed. `expired` is a pinned view whose builder has left the library. `chat` is a greeting or a thank you, not a figure at all. A client must not render `failed` or `chat` as a measurement.
        - `headline` object
          - `value` string, required
          - `label` string, required
          - `tone` 'neutral' | 'brand' | 'good' | 'warning' | 'serious' | 'critical' | 'info'
        - `explanation` string, required: What was computed, and from which records. This is what makes the figure checkable.
        - `table` object
          - `columns` string[], required
          - `rows` array[], required
            - union[]
              - …
          - `align` string[]
        - `chart` object
          - `kind` 'bar' | 'line' | 'donut', required
          - `labels` string[], required
          - `series` object[], required
            - `key` string, required
            - `label` string, required
            - `values` number[], required
          - `format` 'money' | 'percent' | 'number'
        - `list` object[]
          - `label` string, required
          - `sublabel` string
          - `value` string
          - `tone` 'neutral' | 'brand' | 'good' | 'warning' | 'serious' | 'critical' | 'info'
          - `link` string
          - `quote` string: A document’s own words where it matched the question.
          - `documentId` string: The document this row is, for a filing answer.
        - `links` object[]: Where to go for the full detail.
          - `label` string, required
          - `to` string, required
        - `sourceCount` number, required: How many records the figure was derived from. This is what makes it trustworthy.
        - `sourceLabel` string, required
        - `action` object: The first of `actions`, kept for a reader built before an answer could propose more than one.
          - `action` string, required: The operationId to run, as the MCP catalogue names it.
          - `summary` string, required: What running it does, in one sentence.
          - `path` object
          - `query` object
          - `body` unknown
        - `actions` object[]: Every change the assistant worked out and did not make, in the order it means them to be made. Nothing happens until each is sent to `POST /ask/actions`.
          - `action` string, required: The operationId to run, as the MCP catalogue names it.
          - `summary` string, required: What running it does, in one sentence.
          - `path` object
          - `query` object
          - `body` unknown
        - `handoff` boolean: True when the person asked for a human, so the chat is for the helpdesk.
      - `context` string: What a written answer was worked out from. Send it back with the turn in `history`, so a follow-up builds on it.
    - `followUps` string[], required: What to ask next. Suggestions when there was no answer.
    - `conversationId` string, nullable, required: The conversation this turn was written into, or null when the request named none and nothing was kept.
  - `meta` object, required
    - `requestId` string, required: Echoed in the x-request-id header. Quote it in a bug report.

## Other responses

- `400`: The question was empty, over 2000 characters, or the body was not an object.
- `429`: Asking faster than the engine answers.

---

[API](https://skmtc.dev/dardaris/apis/dardaris-api.md) · [All operations](https://skmtc.dev/dardaris/apis/dardaris-api/llms.txt) · [OpenAPI document](https://skmtc.dev/dardaris/apis/dardaris-api/revisions/4aff33835c73?raw)
