This page is a complete, self-contained reference for building against Tako’s agents — the Search Agent (schema-defined structured data + cards) and the Answer Agent (a synthesized, cited answer + cards). Every example runs after you supply an API key, every parameter is grounded in the live API, and the common mistakes are called out explicitly. For narrative introductions see the Agents overview, Search Agent, and Answer Agent.
The two agents
Both agents share one lifecycle — dispatch a run, then poll or stream to a terminal status — and one request shape. They differ only in what the result carries.
| Agent | Endpoint | Returns |
|---|
| Search Agent | /v1/agent/search/runs | Schema-defined structured_output (via output_schema) + cards + answer + citations. |
| Answer Agent | /v1/agent/answer/runs | A synthesized answer + citations + cards. No structured output, ever. |
Reach for an agent when a question needs figuring out rather than retrieving a known value — resolving a cohort (“which companies match…”), ranking or filtering a set by criteria, or multi-hop aggregation. For a specific, known value, time series, or direct comparison, use one-shot Search or Answer instead — one fast, synchronous call.
A run is asynchronous and long-running (it can take minutes): you dispatch a run, then poll it — or stream it — until it reaches a terminal status.
- Base URL:
https://tako.com/api/
- Auth: send your API key in the
X-API-Key request header (not a bearer token). Create a key in the Tako console.
- Dispatch:
POST /v1/agent/{search,answer}/runs → 202 with a run object (status: "queued").
- Poll:
GET /v1/agent/{search,answer}/runs/{run_id} until status is completed or failed.
- List:
GET /v1/agent/{search,answer}/runs returns the caller’s runs, newest first.
Match the path to the agent. A run dispatched at /v1/agent/search/runs must be polled at /v1/agent/search/runs/{run_id} — the search and answer collections are separate. A thread_id is likewise pinned to one product; a follow-up must use the same agent.
Install
pip install tako-sdk # Python
npm install tako-sdk # TypeScript / JavaScript
Dispatch and poll
Dispatch returns immediately with a run_id; poll until the run is terminal. The example uses the Search Agent — the Answer Agent is identical with client.agent.answer.* and the /v1/agent/answer/runs path.
# 1. Dispatch — returns 202 with {"run_id": "...", "status": "queued"}.
curl -X POST https://tako.com/api/v1/agent/search/runs \
-H "X-API-Key: $TAKO_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"query": "Largest US semiconductor companies by 2024 revenue and YoY growth"
}'
# 2. Poll with the run_id until status is "completed" or "failed".
curl https://tako.com/api/v1/agent/search/runs/RUN_ID \
-H "X-API-Key: $TAKO_API_KEY"
import os
import time
from tako import Configuration
from tako.lib import Tako
from tako.models.search_agent_run_request import SearchAgentRunRequest
config = Configuration()
config.api_key["apiKey"] = os.environ["TAKO_API_KEY"]
client = Tako(config)
run = client.agent.search.run(
SearchAgentRunRequest(query="Largest US semiconductor companies by 2024 revenue and YoY growth")
)
# Runs can take minutes — poll on a short interval until terminal.
while run.status not in ("completed", "failed"):
time.sleep(3)
run = client.agent.search.get(run.run_id)
if run.status == "completed":
print(run.result.answer)
for card in run.result.cards or []:
print(card.title, card.webpage_url)
import { Tako } from "tako-sdk";
const tako = new Tako({ apiKey: process.env.TAKO_API_KEY! });
let run = await tako.agent.search.run({
query: "Largest US semiconductor companies by 2024 revenue and YoY growth",
});
// Runs can take minutes — poll on a short interval until terminal.
while (run.status !== "completed" && run.status !== "failed") {
await new Promise((r) => setTimeout(r, 3000));
run = await tako.agent.search.get(run.run_id);
}
if (run.status === "completed") {
console.log(run.result?.answer);
for (const card of run.result?.cards ?? []) {
console.log(card.title, card.webpage_url);
}
}
Stream live progress (SSE)
To show progress while an agent works, stream the run over Server-Sent Events instead of polling. Send Accept: text/event-stream on dispatch (or on the poll endpoint to resume). client.agent.search.stream() / client.agent.answer.stream() yield per-product envelope events (SearchAgentStreamEnvelope / AnswerAgentStreamEnvelope); the terminal agent_result event carries the same result you’d get from polling.
import os
from tako import Configuration
from tako.lib import Tako
from tako.models.search_agent_run_request import SearchAgentRunRequest
config = Configuration()
config.api_key["apiKey"] = os.environ["TAKO_API_KEY"]
client = Tako(config)
req = SearchAgentRunRequest(query="Largest US semiconductor companies by 2024 revenue and YoY growth")
with client.agent.search.stream(req) as stream:
for event in stream:
block = event.block.actual_instance
print(f"seq={event.seq} kind={block.kind}")
if block.kind == "agent_result":
print(block.data.answer)
# The stream ends at stream_done. If it ended without a terminal result
# (but produced at least one event, so run_id is known), poll for status:
if stream.result is None and stream.run_id is not None:
run = client.agent.search.get(stream.run_id)
print(run.status)
curl -X POST https://tako.com/api/v1/agent/search/runs \
-H "X-API-Key: $TAKO_API_KEY" \
-H "Content-Type: application/json" \
-H "Accept: text/event-stream" \
-d '{"query": "Largest US semiconductor companies by 2024 revenue and YoY growth"}'
Each stream envelope carries five fields:
| Field | Type | Description |
|---|
seq | integer | Monotonic resume cursor (non-contiguous — see note below). |
run_id | string | The run this event belongs to. |
thread_id | string | null | The thread the run belongs to. |
category | string | Coarse event class: content, activity, or control. |
block | object | The event payload; its kind field selects the shape below. |
Stream event kinds
block.kind is one of thirteen public kinds. Fields marked (opt) may be absent.
kind | Fields |
|---|
status | message, parent_id (opt) |
tool_call | id, tool, status_message (opt), parent_id (opt), done (bool) |
tool_result | id, tool, elapsed_ms (int), link (opt), parent_id (opt) |
tool_error | id, tool, error, parent_id (opt) |
tool_retry | id, tool, error, elapsed_ms (int), parent_id (opt) |
subagent | agent_id, subagent_type, event ("dispatch" | "complete"), parent_id (opt) |
reasoning | id, delta (text chunk), done (bool) |
text | id, delta (text chunk), done (bool) |
data_pipeline_answer | id, chart_refs (array of strings) |
agent_result | id, data (a SearchAgentResult or AnswerAgentResult — see below) |
heartbeat | (no fields — keep-alive) |
stream_reset | (no fields — discard buffered state and resume) |
stream_done | (no fields — terminal; the stream ends) |
The stream terminates at stream_done. The structured result arrives in the terminal agent_result event, whose data is the per-product result — the same result you’d get from polling.
seq is a monotonic resume cursor, but values are not contiguous — the public stream omits internal events, so gaps are expected. If a stream drops, reconnect and pass the last seq you saw as starting_after (or the Last-Event-ID header) when reading GET /v1/agent/{search,answer}/runs/{run_id} with Accept: text/event-stream.
Request parameters
Both dispatch bodies share these parameters. Only query is required.
| Parameter | Type | Default | Description |
|---|
query | string | — (required) | The research question. |
thread_id | string (uuid) | null | none → new thread | Continue a prior conversation — pass an earlier run’s thread_id. Pinned to one product and one source_indexes. |
effort | "medium" | medium | The agents run a single effort tier; only "medium" is currently accepted. |
source_indexes | array of "data" | "web" | ["data", "web"] | Restrict which sources the agent researches. A flat array, not the sources object used by one-shot Search/Answer. Legacy "tako" maps to "data". |
locale | string | en-US | A non-English locale makes the agent answer in that language. |
timezone | string | null | none | IANA timezone; affects card image rendering only, not the data. |
output_settings | object | null | none | image_dark_mode (bool, default dark). |
The Search Agent accepts two additional parameters:
| Parameter | Type | Default | Description |
|---|
cards | boolean | true | Whether the agent may build visualization cards. Permissive: true means it may, not that it must (a completed run with no cards is legitimate); false suppresses card building. |
output_schema | object | null | none | JSON Schema for structured output. |
The Answer Agent takes no cards or output_schema — its contract is frozen to answer + cards + citations + metadata.
Sources differ from one-shot Search/Answer. The agents take source_indexes — a flat array that restricts sources (["data"], ["web"], or both). One-shot Search and Answer take a sources object ({ "data": {} }). Don’t pass one shape where the other is expected.
Use both sources. source_indexes defaults to ["data", "web"] — both. Leave it off so the agent researches Tako’s curated data and the live web; set it only to deliberately restrict to one index.
# Continue a thread, and restrict to Tako's curated data:
follow_up = client.agent.search.run(
SearchAgentRunRequest(
query="Now break the leader's revenue down by segment",
thread_id=run.thread_id,
source_indexes=["data"],
)
)
Structured output (Search Agent)
Pass an output_schema (JSON Schema) to shape the response into machine-usable JSON in result.structured_output. Two kinds of field:
- Synthesized fields — ordinary schema properties the agent writes.
- Dataset slots — a property marked
"x-tako-dataset": true, filled with the exact retrieved rows as a TakoDataset. A slot node carries only x-tako-dataset, an optional description, and an optional columns hint — no type.
Supported subset: object / array / string / number / integer / boolean, plus enum, required, description. Caps: 16 KB, depth 5, 64 properties, 4 dataset slots. A schema outside these bounds returns 400 output_schema_invalid.
import jsonschema
from tako.lib import Tako, TakoDatasetView, derive_response_schema
from tako.models.search_agent_run_request import SearchAgentRunRequest
schema = {
"type": "object",
"properties": {
"summary": {"type": "string", "description": "One-sentence summary."},
"companies": {"x-tako-dataset": True, "columns": ["company", "revenue_usd", "yoy_growth_pct"]},
},
"required": ["summary"],
}
run = client.agent.search.run(
SearchAgentRunRequest(query="Largest US semiconductor companies by 2024 revenue", output_schema=schema)
)
run = client.agent.search.get(run.run_id) # poll to a terminal status
if run.result and run.result.structured_output:
jsonschema.validate(run.result.structured_output, derive_response_schema(schema))
view = TakoDatasetView(run.result.structured_output["companies"])
print(view.records) # list[dict], one per row
print(view.to_dataframe()) # typed pandas DataFrame (needs tako-sdk[pandas])
import { Tako, TakoDatasetView, deriveResponseSchema } from "tako-sdk";
const tako = new Tako({ apiKey: process.env.TAKO_API_KEY! });
const schema = {
type: "object",
properties: {
summary: { type: "string" },
companies: { "x-tako-dataset": true, columns: ["company", "revenue_usd", "yoy_growth_pct"] },
},
required: ["summary"],
};
const dispatched = await tako.agent.search.run({ query: "...", output_schema: schema });
const run = await tako.agent.search.get(dispatched.run_id); // poll to a terminal status
const output = run.result?.structured_output;
if (output) {
const view = new TakoDatasetView(output.companies);
console.log(view.records);
}
When output_schema is supplied, the result adds:
| Field | Description |
|---|
structured_output | The caller-shaped JSON. Dataset slots hold a TakoDataset envelope of exact rows, or null when honestly unfilled. |
structured_output_status | complete | partial | failed. |
unfilled_fields | Dot-separated paths of honestly-unfilled dataset slots; present iff status is partial. |
structured_output_citations | Field-path → citation-index map, joining the same [n] registry as the answer. |
structured_output_error | An ErrorObject explaining why structured output failed; present iff status is failed. |
Run lifecycle and result shape
Dispatch and poll both return the run object (SearchAgentRun or AnswerAgentRun):
{
"run_id": "run_8sKd2m1f0aQ",
"object": "agent.run",
"thread_id": "1f0e9c34-2b7a-4f0a-9c2e-7b1a2c3d4e5f",
"status": "completed",
"created_at": "2026-06-22T18:04:11+00:00",
"completed_at": "2026-06-22T18:06:48+00:00",
"result": {
"answer": "Nvidia led with about $60.9B in FY2024 revenue (+126% YoY) [1].",
"cards": [
{
"card_id": "mA-C6b48zxyU3G33JJdO",
"title": "US Semiconductor Revenue, FY2024",
"webpage_url": "https://tako.com/card/mA-C6b48zxyU3G33JJdO/",
"image_url": "https://tako.com/api/v1/image/mA-C6b48zxyU3G33JJdO/",
"embed_url": "https://tako.com/embed/mA-C6b48zxyU3G33JJdO/",
"source_indexes": ["data"]
}
],
"citations": [
{ "index": 1, "title": "US Semiconductor Revenue, FY2024", "source_name": "Company Filings (10-K)", "source_index": "data" }
],
"structured_output": null,
"request_id": "f20f965b-6bbd-40df-b50b-a8861f34df24"
},
"error": null
}
| Field | Type | Description |
|---|
run_id | string | The run identifier — poll with it. |
status | string | queued → running → completed | failed. |
thread_id | string | The thread this run belongs to — reuse it to continue the conversation. |
result | object | null | Populated when status is completed. |
error | object | null | An ErrorObject (code, message) when status is failed. |
usage | object | null | Credit/token usage for the run. |
The result shape depends on the agent:
SearchAgentResult — answer (markdown with [n] markers), cards[], citations[], metadata, request_id, plus the structured_output* fields above when output_schema was supplied.
AnswerAgentResult — answer, cards[], citations[], metadata, request_id. No structured_output, no inline data, no web_results — ever. Prose-only (empty cards) is legitimate.
citations is a single top-level registry the answer’s [n] markers join. Each entry has index and title; the agents populate source_index (data | web), and the Search Agent additionally fills excerpt / publish_date for web sources.
There is no web_results field. Web and Tako sources alike land in the unified top-level citations registry.
List past runs
GET /v1/agent/{search,answer}/runs returns the caller’s runs for that agent, newest first, as a list envelope: { "object": "list", "data": [ … ], "has_more": bool, "next_cursor": string | null }. Each item is a trimmed run summary (run_id, status, created_at, completed_at, thread_id, usage) — fetch full detail via the poll endpoint. Paginate with cursor (the prior response’s next_cursor) and limit (default 20, max 100).
curl "https://tako.com/api/v1/agent/search/runs?limit=20" \
-H "X-API-Key: $TAKO_API_KEY"
When to use which
- Search Agent — when you want machine-usable structured data out. Define an
output_schema; get synthesized fields plus dataset slots of exact rows, alongside cards. Ideal for feeding a database, table, or downstream code.
- Answer Agent — when you want a written, citation-backed answer synthesized from everything the agent found, with the cards that support it. Ideal for a chat reply or a briefing.
- Neither — for a specific, known value, time series, or direct comparison, don’t dispatch a run. One-shot Answer (prose) or Search (raw cards) returns it in one fast, synchronous call.
Common mistakes
Avoid these — they are the patterns coding agents most often get wrong.
| Wrong | Correct |
|---|
Authorization: Bearer $KEY | X-API-Key: $KEY |
Expecting 200 from dispatch | Dispatch returns 202 with status: "queued" — then poll |
Reading result immediately | result is null until status is completed — poll to a terminal status |
Polling a search run at /v1/agent/answer/runs/{id} | Poll at the same collection you dispatched to (/v1/agent/search/runs/{id}) |
"source_indexes": { "data": {} } (object) | "source_indexes": ["data"] (array — agents, not one-shot Search/Answer) |
"effort": "high" or "low" | Only "medium" is accepted for the agents |
Reading result.web_results | Use result.citations — there is no web_results field |
Sending output_schema to the Answer Agent | output_schema is Search Agent only; the Answer Agent never returns structured output |
Treating seq as contiguous | seq is a resume cursor only; gaps are expected |
Dispatching into a busy thread_id | A thread allows one in-flight run — a second dispatch returns 409 |
| Continuing a thread with the other agent | A thread_id is pinned to one product — a mismatch returns 409 thread_product_mismatch |
Errors
Failures return an ErrorObject body: { "code": "...", "message": "..." }.
| Status | Meaning | Fix |
|---|
400 | Malformed request (bad JSON, invalid parameter, or output_schema_invalid). | Validate the body against the parameters above. |
401 | Missing or invalid API key. | Send a valid key in the X-API-Key header. |
402 | Out of credits (insufficient balance). | Top up credits — see Credits & billing. |
404 | Unknown run_id, or a thread_id you don’t own. | Use a run_id returned by dispatch. |
409 | Conflict — conflict (thread has an in-flight run), thread_product_mismatch (thread started on the other agent), or source_indexes_mismatch (a follow-up changed the thread’s pinned sources). | Wait for the in-flight run, use the matching agent, or keep source_indexes consistent within a thread. |
500 | Server error. | Retry; if it persists, contact support with the run_id. |