Skip to main content

Agent Query (Legacy)

POST /v1/agent/query is the legacy alias of POST /v1/ask: same read-only turn, same flat charge, with ?wait=<seconds> for a bounded synchronous 200 answer.

POST /v1/agent/query is the legacy alias of [POST /v1/ask](post-ask). Both endpoints start exactly the same read-only agent turn over the workspace corpus, and both charge the same flat agent_ask unit (100 credits per accepted question, idempotent per turn). The difference is the result surface: the ask endpoints return the full payload with the verification verdict and usage, while the query poll returns a compact {status, answer, tier, citations} body. Scoping, conversations, model selection, output formatting, and streaming likewise exist only on the ask surface. New integrations should use /v1/ask; this alias remains supported for integrations built against the original surface.

The request body carries one field, query (up to 20,000 characters). By default the submission answers 202 Accepted with a turnId; poll GET /v1/agent/query/:id roughly every 2 seconds until status leaves processing. Like the ask surface, the alias requires a workspace-scoped key with the read scope, runs the turn under the least-privilege viewer role with the key's minting user's document visibility, and is tenant-isolated: a turn started by another workspace reads as 404.

Bounded synchronous mode: ?wait

The route is not 202-only: ?wait=<seconds> opts into a bounded synchronous answer. Simple retrieval questions are tried against a cheap deterministic tier before any loop turn starts, so when that fast path answers, the answer already exists before the response is written. With wait, an answer produced inside your budget returns 200 OK with the completed payload — turnId, status: "completed", answer, tier, and citations[] — saving the poll round-trip entirely. Anything else, including a fast-path answer that overran the budget and every question that falls through to the full agent loop, returns the byte-identical 202 {turnId}.

wait is bounded by a configured ceiling (10 seconds by default): a value that is not a number between 0 and the ceiling is rejected with 400 naming the valid range, so a typo never silently degrades to async. An overrun budget costs nothing — the answer is durable the moment it is produced, so your very first poll returns it. Loop turns take 10-60 seconds and are never held open by wait; treat the 202 branch as the normal case and the 200 as a latency optimization for retrieval-shaped questions.

The answer text is a narrative: for fast-path turns it carries the deterministic answer, an echo of the query spec the tier compiled, the coverage it computed, and a source line per cited cell; the raw one-line answer as the deterministic templates rendered it is additionally persisted under the turn's retrieval.answer metadata. For full loop turns, answer is the assistant narrative plus a markdown projection of any generative cards the agent produced (tables, ranked lists), so a headless consumer receives the whole deliverable, not a caption pointing at an invisible table.

Metering is identical in both modes: one flat agent_ask unit per accepted question, keyed to the turn. wait changes what is RETURNED, never what is charged — a 200, a 202, and a poll of the same turn all bill exactly once.
POST/v1/agent/query

Query parameters

waitnumberOptional. Seconds to hold the request open for an inline answer (0 up to the configured ceiling, 10 by default). Absent = always 202. Out-of-range or non-numeric values are rejected with 400.

Request body (JSON)

query*stringThe natural-language question, up to 20,000 characters.
GET/v1/agent/query/:id

curl — synchronous when possible (?wait)

curl -s -X POST "https://api.talonic.com/v1/agent/query?wait=5" \
  -H "Authorization: Bearer $TALONIC_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"query": "What is the total gross amount across June invoices?"}'

Response (200 — answered within the wait budget)

{
  "turnId": "7c2f1e04-51ab-4b8e-9d2f-3a6c8e0b4d21",
  "status": "completed",
  "answer": "The total gross amount across June invoices is EUR 48,200.00...",
  "tier": "retrieval_fast_path",
  "citations": [
    {
      "quote": "48,200.00",
      "document_id": "c3d4e5f6-a7b8-9012-cdef-123456789012",
      "kind": "cell",
      "reference": "bruttobetrag",
      "filename": "invoice-0847.pdf",
      "app_url": "https://app.talonic.com/documents/c3d4e5f6-..."
    }
  ]
}

curl — asynchronous (default 202 + poll)

TURN_ID=$(curl -s -X POST https://api.talonic.com/v1/agent/query \
  -H "Authorization: Bearer $TALONIC_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"query": "Summarize our open invoices by vendor."}' | jq -r '.turnId')

curl -s https://api.talonic.com/v1/agent/query/$TURN_ID \
  -H "Authorization: Bearer $TALONIC_API_KEY"

Poll response fields

statusstringprocessing, completed, or error.
answerstringThe final answer narrative as markdown, including any generative-card tables projected to markdown. Empty string until the turn completes.
tierstringWhich engine answered: retrieval_fast_path for the deterministic tier, agent_loop for a full loop turn.
citationsarrayStructured citations: quote, document_id, kind, reference, filename, app_url. Same shape as on POST /v1/ask. Empty until the turn completes.
The 200 body of a ?wait request is the poll body plus turnId, built from the same persisted values a later poll of the same turn returns — the two surfaces can never disagree about what was answered or cited.

Errors

Error responses

400validation_errorMissing or over-length query, the API key is not workspace-scoped, or wait is present but not a number of seconds within the configured ceiling.
401unauthorizedMissing or invalid API key.
402insufficient_creditsBalance cannot cover one agent_ask unit.
404not_foundOn the poll: no turn with this id exists in your workspace.

Frequently asked questions

Should I use /v1/agent/query or /v1/ask?+
Use POST /v1/ask for anything new: it runs the identical turn at the identical price but adds scoping, conversations, model selection, output formatting, streaming, the verification verdict, and usage reporting. The query alias exists so integrations built against the original minimal surface keep working.
When does ?wait return 200 instead of 202?+
Only when the deterministic retrieval fast path produces the answer within your budget. That tier handles simple lookup and aggregate questions; anything routed to the full agent loop returns 202 immediately, because loop turns take 10-60 seconds and are never held open by wait.
What happens if the answer takes longer than my wait budget?+
Nothing is lost: you get the byte-identical 202 {turnId}, and because the answer is persisted the moment it is produced, your first poll of GET /v1/agent/query/:id returns it. An overrun budget costs no extra credits and aborts no server work.
How large can wait be?+
From 0 up to a configured ceiling, 10 seconds by default. A value outside that range — or a non-numeric one — is rejected with a 400 naming the valid range rather than being silently ignored, so a typo cannot masquerade as the feature failing.
Does using wait cost more?+
No. Both modes charge one flat agent_ask unit (100 credits) per accepted question, keyed to the turn so retries never double-bill. wait changes only what is returned, never what is metered.
Can I poll a turn started via /v1/agent/query through /v1/ask/:id?+
The turn id is the same underlying identifier, but treat the two surfaces as separate contracts: poll the surface you submitted on, and migrate the submission to /v1/ask when you want the richer payload.