Skip to documentation

API & MCP

Structured query (v1)

POST /api/v1/query is the machine-readable front door to the semantic ↔ data layer: a third-party system sends a question and receives the full grounded result as one JSON document — the answer, its atomic claims, the document-source registry ([Sn]), the live data-call registry ([Dn]), the entities detected in the question, a groundedness verdict, the answer-policy block, and token usage. No screen scraping, no prose parsing. The authoritative schema is the OpenAPI document — browse it in the interactive API reference or fetch GET /api/v1/openapi.json directly.

POST/api/v1/queryBearer · agents:run

Runs the structured query pipeline: retrieval over the workspace's documents, deterministic entity detection, optional governed live-data lookups, one chat-model generation, and a best-effort groundedness check. The workspace is resolved from the API key — it never appears in the URL or payload.

Request body

questionrequiredstringThe question to answer (1–4000 characters).
includeDatabooleanAlso execute the data bindings of entities detected in the question (at most 3 governed read-only calls) and ground the answer in their rows as [Dn] citations. Default false.
maxSourcesinteger (1..20)Cap on retrieved document chunks — the [Sn] budget. Default 8.
policyModestring (off | shadow | enforce)Per-call override of the workspace's answer-policy mode — the explicit override wins for this request only. Omitted ⇒ the workspace setting applies. Use it to rehearse enforce on a shadow workspace (or silence gating for one call) without touching settings.
Example requestbash
curl -X POST https://your-host/api/v1/query \
  -H "Authorization: Bearer $ELI_KEY" \
  -H "Content-Type: application/json" \
  -d '{"question":"Is anything stuck on the Order Platform right now?","includeData":true}'
Responsejson
{
  "answer": "The Order Platform currently reports 14 stuck orders in the fulfillment queue [D1]. Per the incident runbook, stuck orders older than 24 hours should be requeued from the fulfillment console [S1].",
  "claims": [
    { "text": "The Order Platform currently reports 14 stuck orders in the fulfillment queue", "citations": ["D1"] },
    { "text": "Stuck orders older than 24 hours should be requeued from the fulfillment console", "citations": ["S1"] }
  ],
  "sources": [
    {
      "id": "S1",
      "docId": "01KY10AAAAAAAAAAAAAAAAAAAA",
      "docTitle": "Order Platform incident runbook",
      "docPath": "ops/order-platform-runbook.md",
      "chunkId": "01KY10BBBBBBBBBBBBBBBBBBBB",
      "snippet": "When orders remain in STUCK status for more than 24 hours, requeue them from the fulfillment console…"
    }
  ],
  "dataCalls": [
    {
      "id": "D1",
      "dataCallId": "01KY10CCCCCCCCCCCCCCCCCCCC",
      "connectorName": "northwind-ops",
      "queryTitle": "Stuck orders by age",
      "rowCount": 14,
      "executedAt": "2026-07-21T09:14:03.412Z"
    }
  ],
  "entities": [
    { "id": "01KY0ZW3B8AVDR67NXSE96P81N", "name": "Order Platform", "type": "system", "pagerank": 0.0421, "authority": "certified", "lifecycleStatus": "published" }
  ],
  "retrieval": { "topScore": 0.83, "meanScore": 0.61, "chunkCount": 8, "channels": { "vector": 5, "fts": 3 } },
  "retrievalDebug": {
    "understanding": { "rewrite": "stuck orders on the Order Platform right now", "subQueries": ["Order Platform stuck order backlog"], "isAmbiguous": false },
    "stages": { "candidatePool": 40, "graphAdded": 4, "reranked": true, "assembledTokens": 3120 },
    "reranker": "voyage"
  },
  "groundedness": { "verified": 2, "total": 2 },
  "policy": {
    "mode": "enforce",
    "verdict": "answer",
    "confidence": 0.78,
    "calibrationVersion": "bootstrap-v1",
    "reasons": ["retrieval_above_floor", "confidence_above_answer_floor"],
    "caveats": [],
    "conflicts": []
  },
  "model": { "chat": "claude-sonnet-4-5", "judge": "claude-haiku-4-5" },
  "usage": { "inputTokens": 4210, "outputTokens": 312, "costUsd": 0.0173 },
  "generatedAt": "2026-07-21T09:14:05.001Z"
}

Response fields

answerrequiredstringThe answer text with inline [Sn]/[Dn] citation markers.
claimsrequiredClaim[]The answer decomposed into atomic claims: { text, citations }. Citations reference ids from sources[] and dataCalls[] only — ids that don't exist in this answer's registries are stripped.
sourcesrequiredSource[][Sn] registry — the retrieved document chunks the answer may cite: { id, docId, docTitle, docPath, chunkId, snippet }. snippet is the first 200 characters of the chunk.
dataCallsrequiredDataCall[][Dn] registry — governed live-data lookups that grounded this answer: { id, dataCallId, connectorName, queryTitle, rowCount, executedAt }. dataCallId is the persisted data_calls provenance row. Empty unless includeData was true and a detected entity had executable bindings; at most 3 entries.
entitiesrequiredEntity[]Entities detected in the question (deterministic name/alias matching, no LLM): { id, name, type, pagerank, authority, lifecycleStatus }. This is the explainability trail for which data sources were consulted. pagerank is null until graph analytics have run. authority/lifecycleStatus carry the concept-governance state so consumers can weight assertions; draft and pending-review entities are never surfaced here.
retrievalrequiredobject | nullRetrieval diagnostics for the [Sn] pass: { topScore, meanScore, chunkCount, channels } — channels maps each retrieval channel to how many of the served chunks it contributed. Null when retrieval returned nothing. Use it to tell a thin answer (weak retrieval) from a confident one before trusting the prose.
retrievalDebugobject?Retrieval-pipeline trace, present only when a stage beyond raw hybrid retrieval ran: { understanding, stages, reranker }. understanding is the query-understanding result ({ rewrite, subQueries, isAmbiguous, filters }) or null; stages carries the per-stage counts ({ candidatePool, graphAdded, reranked, assembledTokens }); reranker is the resolved reranker kind (voyage | cohere | onnx-bge | none). Additive and non-breaking — absent on the raw-retrieve baseline. See Retrieval pipeline.
groundednessrequiredobject | nullBest-effort judge verdict: { verified, total } — how many claims held up against the evidence. Null when no judge model is configured or the check failed; a judge outage never fails the answer.
policyrequiredPolicyBlock | nullThe answer-policy verdict for this request: { mode, verdict, confidence, calibrationVersion, reasons, caveats, conflicts, clarify?, pointers? }. verdict is a post verdict (answer | answer_with_caveat | abstain) on generated answers, or a pre verdict (clarify | abstain_with_pointers) on enforce-mode short-circuits. caveats carry typed warnings (no_verified_source, unsupported_claims, conflicting_evidence, low_confidence); conflicts list detected evidence disagreements with both stances. Null when the policy engine is off.
modelrequiredobject{ chat, judge? } — the model ids used. judge is present only when a groundedness check actually ran.
usagerequiredobject | null{ inputTokens, outputTokens, costUsd? } for the generation call. costUsd is a number and is omitted when the model has no known pricing.
generatedAtrequiredstring (date-time)ISO 8601 timestamp of when the answer was produced.

Trust signals ride along

entities[].authority is the human-vetting tier from concept governance certified is the only tier an assistant states without hedging — and retrieval tells you how strong the evidence pass was. The policy block distills both (plus groundedness) into one calibrated verdict, so a consumer can branch on policy.verdict instead of re-deriving trust from the raw signals.

Enforce-mode short-circuits

When the workspace (or the per-call policyMode) is enforce and the pre-gate returns clarify or abstain_with_pointers, no generation happens. The response is still a complete StructuredAnswer: answer is a deterministic template (the clarifying question, or a short abstention), claims is empty and cites nothing, usage reports zero model tokens, and the policy block carries the verdict — clarify includes clarify.question + options (disambiguated entities as Name (type)), and abstain_with_pointers includes pointers to the closest documents (also mirrored into sources), never a bare refusal. In shadow mode the same verdicts are computed and logged as policy decisions but the answer is generated exactly as if the policy were off.

How the pipeline runs

  1. Model resolution first — a workspace without an enabled chat model gets 409 no-model-configured before any retrieval work happens.
  2. Semantic layer — the retrieval pipeline (query understanding, graph-fused hybrid retrieval, cross-encoder rerank, token-budget assembly) produces up to maxSourceschunks, and deterministic entity detection runs alongside it. Entity detection never calls a model; the pipeline's understanding and rerank stages are network calls made outside any transaction, and each degrades to raw hybrid retrieval when unavailable — so this step is byte-identical to the old direct-retrieve path when no pipeline stage is active. The retrievalDebug block reports which stages ran.
  3. Policy pre-gate — the answer policy checks the retrieval floor, runs the deterministic entity-ambiguity check, and (when enabled) one judge-slot call for chunk relevance, ambiguity, and source disagreement. In enforce mode a non-proceed verdict short-circuits here; on proceed, a detected disagreement is threaded into the generation prompt as a conflict hint so the answer presents both accounts.
  4. Data layer (opt-in) — with includeData: true, the detected entities' bindings execute as governed read-only calls (at most 3). Bindings whose parameters need a model source are skipped — this pipeline supplies only entity-name and constant params.
  5. Generation — one chat-model call with the sources and data rows wrapped in untrusted-content delimiters. Models with native structured output return the claims directly; models that reject JSON-schema output still produce a cited plain-text answer, and claims are then derived deterministically from its [Sn]/[Dn] markers.
  6. Groundedness— the judge model checks the claims against the evidence, best-effort. The same judge batch elicits the model's verbalized confidence (a policy feature, never the score).
  7. Policy post-gate — retrieval, verification, and groundedness features merge into one calibrated confidence; multi-document claims are checked for evidence conflicts; the resulting verdict, caveats, and conflicts ship as the policy block, and both gate decisions are recorded to the decision log.

A broken data source never fails the request

Failed lookups don't surface as 500s — and they don't appear in dataCallseither. If a binding's execution errors, times out, or is blocked, that call is skipped and the answer still ships grounded in whatever succeeded (worst case: documents only). Every failed execution still writes its data_calls provenance row (status error · timeout · blocked), auditable in the workspace Data page's call history — dataCalls[] in the response lists only the lookups that actually grounded the answer.

Errors

Standard conventions from Errors & rate limits apply:

  • 400 — malformed JSON or a body that fails validation (zod issues[] included).
  • 401 unauthorized — missing/invalid/revoked key.
  • 403 insufficient_scope — key lacks agents:run (this call invokes the workspace's chat model).
  • 409 no-model-configured — the chat model slot has no enabled provider.

There is no rate limiting on /api/v1 yet — 429is not currently returned. Treat your key's scopes and your own client-side throttling as the guard rails.

Prefer tools? Use MCP

If the consumer is itself an agent, the eli.ai MCP server exposes this same pipeline as the eli_query tool — the policy block rides along automatically — plus the finer-grained KB and data-layer tools for agents that want to orchestrate retrieval themselves.