Talkory API Documentation

One API key, one call: your prompt runs on up to 5 LLMs (GPT, Claude, Gemini, Grok, Perplexity) and comes back with an AI consensus. Base URL: https://api.talkory.ai.

curl -X POST https://api.talkory.ai/v1/query/sync \
  -H "Authorization: Bearer tk_live_xxxxxxxx" \
  -H "Content-Type: application/json" \
  --max-time 600 \
  -d '{ "prompt": "Best commuter motorcycles under $5000?" }'

# → one JSON: every model's answer + consensus
#   + common answer (can take a few minutes)

Endpoints at a glance

Everything the API can do, in one look. Free endpoints never touch your wallet; Paid endpoints are charged from it. Click any row for full details.

EndpointCostWhat it does
POST /v1/query/syncPaidSimplest way to query. One request → one JSON with all model answers + consensus. Waits until done (can take a few minutes).
POST /v1/queryPaidSame run, but answers stream live over SSE as each model responds.
POST /v1/queries/:id/consensusPaidGenerate consensus later for a query run with consensus: false.
GET /v1/queries/:idFreeFetch a query's full result by id (also used for polling).
GET /v1/queriesFreeList your past queries, newest first.
GET /v1/health/llmFreeLive health of all 5 LLM providers.
GET /v1/modelsFreeModels available for queries.
GET /v1/walletFreeYour wallet balance.
GET /v1/usageFreeRequests, errors and spend for your key.

Running a query: pick your style

You wantUseHow it behaves
Simple request → responsePOST /v1/query/syncConnection stays open until the full answer is ready. May take a few minutes, so set a generous client timeout.
Live tokens as they arrivePOST /v1/querySSE stream: every model's text chunks live, final done event with consensus.
Fire & forget + pollPOST /v1/query with stream: falseReturns 202 { id } instantly; poll GET /v1/queries/:id until done.

Getting started

API access is available on paid plans. Recharge your wallet once in app.talkory.ai and you can create keys.

  1. Sign in to app.talkory.ai → Settings → API Keys.
  2. Click Create key, give it a name, and (optionally) set a monthly spend cap.
  3. Copy the key that starts with tk_live_. It is shown only once, so store it somewhere safe. If you lose it, revoke it and create a new one.
  4. Send it as a Bearer token on every request (see below).

Authentication

Every request must include an Authorization header with your secret key:

Authorization: Bearer tk_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
Keep your key secret. Use it only from your own server. Never embed it in a website, mobile app, or any client-side code where users could read it. If a key is exposed, revoke it immediately from settings.

Scopes

Every key carries a set of scopes (permissions) that you choose when creating it. An endpoint called without the required scope returns 403 insufficient_scope.

ScopeGrantsCost
health:readGET /v1/health/llmFree
models:readGET /v1/modelsFree
wallet:readGET /v1/walletFree
usage:readGET /v1/usageFree
query:readGET /v1/queries, GET /v1/queries/:idFree
query:writePOST /v1/query, POST /v1/query/sync, POST /v1/queries/:id/consensusCharged from wallet

Rate limits

Each key is limited to 30 requests per minute by default. Requests that exceed the limit receive a 429 response with a Retry-After header (seconds to wait). Responses also include X-RateLimit-Limit and X-RateLimit-Remaining.

POST /v1/query and POST /v1/query/sync share an additional limit of 10 query runs per minute and at most 2 queries in flight per key at a time (409 query_in_progress otherwise).

Errors

All errors return a consistent JSON shape:

{
  "error": {
    "code": "rate_limited",
    "message": "Rate limit exceeded. Slow down and retry.",
    "status": 429,
    "requestId": "req_a1b2c3d4"
  }
}

Include the requestId when contacting support. Possible codes:

HTTPcodeWhen it happensWhat to do
400invalid_requestMalformed request or failed validationFix the request format
401missing_api_keyNo Authorization headerSend the Bearer key
401invalid_api_keyKey is wrong, revoked, or expiredCreate a new key in settings
402insufficient_balanceWallet has no balance (paid endpoints)Top up your wallet
402paid_plan_requiredAccount is on the free plan (API access is a paid feature)Recharge your wallet in app.talkory.ai
402spend_cap_reachedMonthly spend cap hit for the keyRaise the cap or wait for next month
403insufficient_scopeKey lacks the required scopeUse a key with the right scope
400invalid_modelUnknown model id, or model not on your planCheck GET /v1/models
404not_foundEndpoint or resource does not existCheck the URL
409query_in_progressToo many queries in flight for this keyWait for a running query to finish
413payload_too_largeRequest body too largeSend a smaller payload
429rate_limitedToo many requestsRetry after Retry-After seconds
500internal_errorUnexpected server errorRetry; contact support with requestId
503service_unavailableTemporary outage / maintenanceRetry shortly

Endpoint reference

GET/v1/health/llmFree · no wallet charge

Returns the current health of every LLM provider Talkory uses: GPT, Claude, Gemini, Grok and Perplexity. Requires a valid key with the health:read scope (the default scope on every new key). Results are cached for up to 5 minutes.

Request
curl https://api.talkory.ai/v1/health/llm \
  -H "Authorization: Bearer tk_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
Node.js
const res = await fetch("https://api.talkory.ai/v1/health/llm", {
  headers: { Authorization: `Bearer ${process.env.TALKORY_API_KEY}` },
});
const data = await res.json();
console.log(data.status, data.providers);
Python
import os, requests

res = requests.get(
    "https://api.talkory.ai/v1/health/llm",
    headers={"Authorization": f"Bearer {os.environ['TALKORY_API_KEY']}"},
)
print(res.json())
Response 200 OK
{
  "status": "ok",
  "checkedAt": "2026-06-27T10:00:00.000Z",
  "providers": {
    "gpt":        { "status": "ok" },
    "claude":     { "status": "ok" },
    "gemini":     { "status": "ok" },
    "grok":       { "status": "error" },
    "perplexity": { "status": "ok" }
  }
}
FieldTypeDescription
statusstringok if all providers are healthy, otherwise degraded
checkedAtstring (ISO 8601)When the health snapshot was taken
providersobjectPer-provider status: each is ok or error
GET/v1/modelsFree · no wallet charge

Lists the models available on the API. Use the returned id values in POST /v1/query. Requires the models:read scope.

Response 200 OK
{
  "models": [
    { "id": "gpt",        "name": "GPT-5.5",             "provider": "OpenAI",        "vision": true  },
    { "id": "claude",     "name": "Claude Sonnet 4.6",   "provider": "Anthropic",     "vision": true  },
    { "id": "gemini",     "name": "Gemini 3.1 Pro",      "provider": "Google",        "vision": true  },
    { "id": "perplexity", "name": "Sonar Reasoning Pro", "provider": "Perplexity AI", "vision": false },
    { "id": "grok",       "name": "Grok 4.3",            "provider": "xAI",           "vision": false }
  ]
}
GET/v1/walletFree · no wallet charge

Your current wallet balance. Requires the wallet:read scope.

Response 200 OK
{ "balance": 12.4831, "currency": "USD", "plan": "payg" }
GET/v1/usageFree · no wallet charge

Aggregated usage for the calling key. Optional ?from= and ?to= ISO timestamps (default: last 30 days). Requires the usage:read scope.

Response 200 OK
{
  "period":   { "from": "2026-06-04T00:00:00.000Z", "to": "2026-07-04T00:00:00.000Z" },
  "requests": { "total": 218, "errors": 3, "byRoute": { "/v1/query": { "requests": 42, "errors": 1 } } },
  "queries":  { "total": 42, "charged": 3.1272, "totalTokens": 913202 }
}
POST/v1/query/syncPaid · charged from wallet

The simplest way to use Talkory: a normal request → response API call. Send your prompt; the connection stays open while every model answers, and you get back one JSON body with all model results, the AI consensus, the common answer and confidence. No SSE to parse, no polling. Requires the query:write scope.

This request can take a few minutes. It waits for all LLM responses, then builds the consensus and common answer before responding. We keep the connection alive for you while the run is in progress, so proxies won't cut it; just set your HTTP client timeout to at least 10 minutes (e.g. curl --max-time 600). If your connection drops mid-run, the run still completes and is charged. Find it via GET /v1/queries and fetch it with GET /v1/queries/:id.

Error handling: if the run itself fails after it has started, the response body contains an error object (with the query id) instead of results, so always check for data.error before reading fields.

Request body
FieldTypeDefaultDescription
promptstring(required)Your question. Input size follows your account's token limits.
modelsstring[]all plan modelsModel ids from GET /v1/models (max 10). Omit to run all models on your plan.
countrystring"global"ISO country code (e.g. "IN") to localise answers.
recursivebooleanfalseRound-2 self-review before consensus (slower, higher quality).
consensusbooleantruefalse skips consensus; generate later via POST /v1/queries/:id/consensus.

Same fields as POST /v1/query, just no stream option.

Request
curl -X POST https://api.talkory.ai/v1/query/sync \
  -H "Authorization: Bearer tk_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" \
  -H "Content-Type: application/json" \
  --max-time 600 \
  -d '{ "prompt": "Best commuter motorcycles under $5000 in 2026?" }'
Node.js
const res = await fetch("https://api.talkory.ai/v1/query/sync", {
  method: "POST",
  headers: {
    Authorization: `Bearer ${process.env.TALKORY_API_KEY}`,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({ prompt: "Best commuter motorcycles under $5000?" }),
});
const data = await res.json(); // waits until the full answer is ready
console.log(data.consensus, data.commonAnswer, data.confidence, data.charged);
Python
import os, requests

res = requests.post(
    "https://api.talkory.ai/v1/query/sync",
    headers={"Authorization": f"Bearer {os.environ['TALKORY_API_KEY']}"},
    json={"prompt": "Best commuter motorcycles under $5000?"},
    timeout=600,  # the run can take a few minutes
)
data = res.json()
print(data["consensus"], data["confidence"], data["charged"])
Response 200 OK
{
  "id": "7f9e4c2a-1b8f-4a21-9d3c-e77aa53b6c10",
  "status": "success",
  "models": ["gpt", "claude", "gemini", "grok", "perplexity"],
  "results": [
    { "modelId": "gpt", "name": "GPT-5.5", "provider": "OpenAI", "score": 91,
      "tokens": 2841, "latency": "9.4s", "text": "## Best commuter motorcycles…" }
  ],
  "consensus": "## Consensus answer…",
  "commonAnswer": "- Honda CB300R, named by every model…",
  "confidence": 87,
  "charged": 0.0742,
  "totalTokens": 14203,
  "createdAt": "2026-07-04T09:12:33.000Z"
}

Same shape as GET /v1/queries/:id. status is success, or partial if some models failed (you are only charged for models that succeed).

POST/v1/queryPaid · charged from wallet

Talkory's core endpoint. Runs your prompt across up to five models in parallel, optionally refines every answer with a recursive-correction round, then produces an AI consensus and a common answer (points all models agree on). Results stream live over Server-Sent Events. Requires the query:write scope. The total cost of the run is charged from your wallet at the end and returned as charged in the final event: one number, no surprises.

Request body
FieldTypeDefaultDescription
promptstring(required)Your question. Input size follows your account's token limits.
modelsstring[]all plan modelsModel ids from GET /v1/models. If provided, must contain at least one valid model id (max 10); an empty array is rejected. Omit the field entirely to run all models active on your plan.
countrystring"global"ISO country code (e.g. "IN") to localise answers. If omitted, defaults to "global".
recursivebooleanfalseRound-2 self-review: each model critiques and improves its own answer before consensus. Defaults to false if omitted.
consensusbooleantrueDefaults to true if omitted. false skips consensus; generate it later with POST /v1/queries/:id/consensus.
streambooleantrueDefaults to true (SSE) if omitted. false returns 202 { "id" } immediately; poll GET /v1/queries/:id.

There is no system-prompt field; output length and input limits come from your account settings in app.talkory.ai. Maximum output tokens apply per model.

Request
curl -N https://api.talkory.ai/v1/query \
  -H "Authorization: Bearer tk_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" \
  -H "Content-Type: application/json" \
  -d '{
    "prompt": "Best commuter motorcycles under $5000 in 2026?",
    "models": ["gpt", "claude", "gemini", "grok", "perplexity"],
    "recursive": false,
    "consensus": true
  }'
Consuming the stream (Node.js)
const res = await fetch("https://api.talkory.ai/v1/query", {
  method: "POST",
  headers: {
    Authorization: `Bearer ${process.env.TALKORY_API_KEY}`,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({ prompt: "Best commuter motorcycles under $5000?" }),
});

const reader = res.body.getReader();
const dec = new TextDecoder();
let buf = "";

while (true) {
  const { done, value } = await reader.read();
  if (done) break;
  buf += dec.decode(value, { stream: true });

  const parts = buf.split("\n\n"); // SSE events are separated by a blank line
  buf = parts.pop();                 // keep the (possibly incomplete) tail

  for (const part of parts) {
    const line = part.trim();
    if (!line.startsWith("data: ")) continue; // skips ": ping" heartbeats
    const raw = line.slice(6);
    if (raw === "[DONE]") continue;           // end-of-stream marker

    const ev = JSON.parse(raw);
    switch (ev.type) {
      case "query_created": console.log("id:", ev.id); break;      // save it
      case "model_chunk":   process.stdout.write(ev.chunk); break; // live text
      case "done":          // final payload lives on this event:
        console.log(ev.consensus, ev.commonAnswer, ev.confidence,
                    ev.charged, ev.balance);
        break;
    }
  }
}

Every event is a data: {...} JSON line with a type field. Switch on type; the done event carries the final fields (consensus, commonAnswer, confidence, charged, balance).

Stream events (SSE)

The response is text/event-stream; each line is data: {...} JSON with a type field, ending with data: [DONE]. Heartbeat comments (: ping) arrive every 15 seconds.

typeMeaning
query_createdFirst event. Contains the query id (a UUID). Save it: if the connection drops, the run still finishes and you can fetch the result by id.
model_stream_startA model produced its first token.
model_chunkLive text chunk from one model (modelId, chunk).
model_resultOne model finished: full text, score, latency, tokens.
model_errorOne model failed or timed out. The run continues with the rest; you are only charged for models that succeed.
round1_done / round2_start / round2_doneRound boundaries (round 2 only when recursive: true; round-1 chunks are suppressed in that mode).
consensus_startConsensus synthesis began.
doneFinal payload: all results, consensus, commonAnswer, confidence, charged, balance.
errorFatal error for the whole run.
Final event done
{
  "type": "done",
  "id": "7f9e4c2a-1b8f-4a21-9d3c-e77aa53b6c10",
  "status": "success",
  "models": ["gpt", "claude", "gemini", "grok", "perplexity"],
  "results": [
    { "modelId": "gpt", "name": "GPT-5.5", "provider": "OpenAI", "score": 91,
      "tokens": 2841, "latency": "9.4s", "text": "## Best commuter motorcycles…", "tags": ["Motorcycles", "Commuter", "Budget"] }
  ],
  "consensus": "## Consensus answer…",
  "commonAnswer": "- Honda CB300R, named by every model…",
  "confidence": 87,
  "charged": 0.0742,
  "balance": 12.4089,
  "totalTokens": 14203,
  "avgLatency": "11.2s",
  "createdAt": "2026-07-04T09:12:33.000Z"
}
Prefer no streaming? Use stream: false

Send "stream": false and the call returns instantly, with no SSE to parse. You get an id, then poll for the result:

# 1. Start the run (returns immediately)
curl -sS -X POST https://api.talkory.ai/v1/query \
  -H "Authorization: Bearer tk_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" \
  -H "Content-Type: application/json" \
  -d '{ "prompt": "Best commuter bikes under $5000?", "stream": false }'
# → 202  { "id": "7f9e4c2a-1b8f-4a21-9d3c-e77aa53b6c10", "status": "running" }

# 2. Poll every 1–2s until status is no longer "running"
curl -sS https://api.talkory.ai/v1/queries/7f9e4c2a-1b8f-4a21-9d3c-e77aa53b6c10 \
  -H "Authorization: Bearer tk_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
# → status "running" → keep polling; "success" / "partial" → full result is ready

The POST answers right away, but the final answer (consensus, results, confidence) only appears once status flips from running to success/partial. Polling with GET /v1/queries/:id is free; only the query itself is charged.

Disconnects are safe. Streaming or not, once a run starts it keeps running on our side, completes, and is charged normally. Fetch the finished result any time with GET /v1/queries/:id using its id.
POST/v1/queries/:id/consensusPaid · idempotent

Generate the consensus + common answer for a query that was run with consensus: false. If the consensus already exists it is returned as-is with charged: 0, so calling this twice never double-charges. Requires query:write.

Response 200 OK
{
  "id": "7f9e4c2a-1b8f-4a21-9d3c-e77aa53b6c10",
  "consensus": "## Consensus answer…",
  "commonAnswer": "- Honda CB300R…",
  "confidence": 87,
  "charged": 0.0031,
  "balance": 12.4058,
  "cached": false
}
GET/v1/queries/:idFree · no wallet charge

Fetch a query and its full results by id, in the same shape as the done event. Use it to poll when running with stream: false, or to recover after a dropped stream (status is "running" until the run finishes). Requires query:read.

GET/v1/queriesFree · no wallet charge

Paginated list of your API queries, newest first. ?limit= (max 100, default 20) and ?cursor= from the previous page's nextCursor. Requires query:read.

Response 200 OK
{
  "queries": [
    { "id": "7f9e4c2a-1b8f-4a21-9d3c-e77aa53b6c10", "prompt": "Best commuter motorcycles…",
      "status": "success", "models": ["gpt","claude","gemini"], "recursive": false,
      "confidence": 87, "charged": 0.0742, "createdAt": "2026-07-04T09:12:33.000Z" }
  ],
  "nextCursor": "d2c8a611-4b0f-4e9a-9f75-3c1b6e0a2d58"
}

FAQ

How do I authenticate with the Talkory API?

Create an API key at app.talkory.ai under Settings, then send it as a Bearer token in the Authorization header on every request. Keys are shown only once and should only be used from server-side code.

What is the base URL for the Talkory API?

All Talkory API endpoints are served from https://api.talkory.ai.

What are the Talkory API rate limits?

Each API key is limited to 30 requests per minute by default. Requests over the limit receive a 429 response with a Retry-After header.

Is the GET /v1/health/llm endpoint free?

Yes. It never touches your wallet or records a charge. It requires the health:read scope, included by default on every new key, and results are cached for up to 5 minutes.

Can I get the full multi-LLM answer in a single request?

Yes. POST /v1/query/sync is a normal request → response call: it waits for all LLM responses, builds the consensus and common answer, and returns everything as one JSON body. It can take a few minutes, so set a generous client timeout (10 minutes is safe).

How do I run a multi-LLM query through the Talkory API?

POST /v1/query with a prompt runs your question across up to five models in parallel and streams every answer plus an AI consensus and common answer over Server-Sent Events. It requires the query:write scope and is charged from your wallet; the total appears as charged in the final event.

What scopes do Talkory API keys support?

health:read, models:read, wallet:read, usage:read, query:read and query:write. Pick them when creating a key in settings; only query:write endpoints are charged.

What does the Talkory health check endpoint return?

A status field (ok or degraded), a checkedAt ISO 8601 timestamp, and a providers object with a per-provider ok/error status for GPT, Claude, Gemini, Grok, and Perplexity.

Need a key? Head to your API settings.