---
title: "Query the tenant's knowledge graph."
method: POST
path: "/v1/query"
tags: ["query"]
---

# Query the tenant's knowledge graph.

`POST /v1/query`

## Request

- Base URL: `https://api.andish.ru` (the document declares no server; this is the origin it was published from)
- URL: `https://api.andish.ru/v1/query`
- Auth: none declared

Run a natural-language query against the tenant's knowledge graph.

**Who answers (ADR-082):** a JWT (dashboard session) request gets a synthesised answer (DeepSeek V3 via OpenRouter) plus best-effort source references. An **API-key request never calls our model at all** — the server returns retrieved material instead (see ``context_only`` and ``hl_keywords``/``ll_keywords`` below for the exact shape); ``metadata.model`` reads ``context-mode-no-llm`` and ``metadata.mode`` reports the search mode actually used (which may not be the one requested — see ``hl_keywords``).

**Empty graph:** If no documents have been indexed yet, the endpoint returns ``answer='Недостаточно данных для ответа.'`` without calling the LLM.

**Cost cap:** Requests are blocked with 402 when the tenant's daily cost cap is reached (``error=cost_cap_exceeded``; comes back within 24 hours), OR — on the free tier — when the calendar-month cap is reached (``error=cost_cap_monthly_exceeded``; the body carries ``retry_after`` seconds and the response the ``Retry-After`` header: the allowance comes back on the 1st of the next month, 00:00 UTC; when both caps are used up the answer is the monthly one), OR when the platform-wide daily spend cap is reached and this tenant is billed on the platform key (``error=platform_budget_paused``, ADR-066 — the body carries no ``cap`` field for this reason, unlike the per-tenant one; a tenant on their own key is never affected). Neither cap applies to an API-key request — it spends nothing.

**modes local / global / naive:** x-experimental — may return an empty answer when the knowledge graph is sparse.

## Request body

- QueryRequest: Request body for POST /v1/query. Modes local / global / naive are experimental — marked x-experimental in OpenAPI description. mix is the recommended production mode.
  - `query` string, required: Natural-language question to ask the knowledge graph.
  - `mode` 'mix' | 'local' | 'global' | 'naive': Retrieval mode. ``mix`` (default) combines knowledge graph + vector search. ``local`` / ``global`` / ``naive`` are x-experimental and may return an empty answer when the graph is sparse.
  - `include_sources` boolean: When true, the response includes best-effort source references.
  - `context_only` boolean: When true, retrieval is performed but the synthesis LLM is NOT called. The response includes a ``context`` object (chunks, entities, relations) instead of a synthesised ``answer``. Use this mode to let your own LLM (e.g. Claude, GPT-4) synthesise the answer from raw retrieval data, which avoids paying for double synthesis. **API-key requests (ADR-082):** this flag is IGNORED for the synthesis decision — a question authenticated with an API key never reaches our model at all, regardless of this value. It only controls the *shape* of ``answer``: ``true`` keeps it empty (use ``context``); ``false`` fills it with the same retrieved material rendered as text, led by a mandatory disclaimer, for legacy clients that expect a ready answer. Only a JWT (dashboard session) request can get a real synthesised answer.
  - `hl_keywords` string[], nullable: High-level (abstract) keywords for the A2 retrieval path. When BOTH hl_keywords and ll_keywords are provided, the platform skips keyword-extraction LLM call (Gemini), making this request completely free of platform LLM cost. Must be provided together with ll_keywords — one without the other is a 422 error. **API-key requests:** with both lists, the graph is searched with them directly; without either, the request falls back to vector-only (``naive``) search. Either way, an API-key question never calls our model (ADR-082).
  - `ll_keywords` string[], nullable: Low-level (specific) keywords for the A2 retrieval path. See hl_keywords for full description.

## Response `200`

Successful Response

- QueryResponse: 200 response for POST /v1/query. In synthesis mode (context_only=False): answer is filled, context is None. In context mode (context_only=True): answer is empty string, context is filled. Wire-contract evolution follows ADR-012 §Stability Contract.
  - `answer` string, required
  - `sources` SourceItemResponse[], required
    - `document_id` string, required
    - `chunk_id` string, nullable
    - `excerpt` string, nullable
    - `reference_id` string, nullable
    - `document_date` string, nullable
    - `loaded_days_ago` integer, nullable
    - `freshness` string, nullable
    - `document_name` string, nullable
  - `metadata` QueryMetadata, required: Per-request telemetry included in every QueryResponse.
    - `mode` string, required
    - `model` string, required
    - `tokens_in` integer, required
    - `tokens_out` integer, required
    - `latency_ms` integer, required
    - `unresolved_sources_count` integer
    - `no_answer_reason` string, nullable
  - `context` QueryContext: Structured retrieval payload for context_only=True mode (AC-2, AC-9).
    - `chunks` ContextChunk[], required
      - `text` string, required
      - `document_id` string, required
      - `chunk_id` string, nullable
      - `document_name` string, nullable
      - `document_date` string, nullable
      - `loaded_days_ago` integer, nullable
      - `freshness` string, nullable
    - `entities` ContextEntity[], required
      - `name` string, required
      - `type` string, nullable
      - `description` string, nullable
    - `relations` ContextRelation[], required
      - `source` string, required
      - `target` string, required
      - `description` string, nullable
      - `keywords` string, nullable
      - `weight` number, nullable
    - `truncated` boolean

## Other responses

- `422`: Validation Error
- `429`: Daily query quota used up, OR too many simultaneous questions from this client (``detail.error = too_many_concurrent_queries``, with ``limit`` and ``retry_after`` = 10; the ``Retry-After: 10`` header says when to retry). Only a question answered by our model (a dashboard session) counts; an API-key question never does.

---

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