What the Data Graph does
Tako serves live financial, macroeconomic, and company data as embeddable knowledge-card charts throughPOST /v3/search. The graph endpoints tell you what data Tako actually has before you search — so you compose queries that hit, pin the exact nodes you resolved, and honestly report gaps.
- Base URL:
https://tako.com/api/ - Auth: every endpoint on this page — including the graph endpoints — takes your API key in the
X-API-Keyrequest header. Create a key in the Tako console. - Cost: graph calls consume no credits. They are bounded by rate limits only: 180/minute and 10,000/day per account. Back off on
429with jittered exponential retry. - Endpoints:
GET /beta/graph/search(resolve a name to nodes),GET /beta/graph/related(explore what a node connects to),GET /beta/graph/node/{id}(confirm what an id refers to — occasional use).
The graph endpoints are beta. A returned node is a guide, not a guarantee — see Knowing what you don’t know for how to report coverage honestly.
Minimal working example
Resolve an entity, read its metrics, then run a search pinned to the resolved node. Complete after you setTAKO_API_KEY — no SDK required, the graph is plain HTTP.
Graph Search — resolve a name to nodes
GET /beta/graph/search resolves free text to entity and metric nodes. Decide up front whether you’re resolving a thing (entity) or a measure (metric) and pass the matching types — don’t mix them in one lookup.
Labels: disambiguating without filtering
Ambiguous names (“air china”, “apple”) resolve to a pile of nodes across types — a company can share its name with a country or a fruit. Alabel biases the ranking toward one entity category (label=ORG floats Air China the airline above China the country). It is a boost, not a filter — matching nodes rank higher, but off-label nodes still return. Use it to steer the top result, not to guarantee exclusivity.
- Values:
PERSON,ORG,GPE,LOC,PRODUCT,EVENT,LANGUAGE,MONEY,METRIC,STOCK_TICKER,WEBSITE. Sports teams areORG. - Auto-detection: with no explicit
label, Tako infers the entities inqand applies the matching boost; the labels it found come back ininferred_labels(empty list = inference ran, found nothing; absent = didn’t run).
Response
null.
Graph Related — explore what a node connects to
GET /beta/graph/related takes a node_id from Graph Search and returns everything it connects to, grouped by relation.
An unknown
relation key returns 200 with empty items, not an error — so a typo’d rel:* key reads as “no relation.” Relation keys are per-node: read the overview to discover them, then drill.Overview response (no relation)
An ordered relations[] list — named semantic edges first, then membership, data co-reference, and siblings. Empty groups are dropped.
kind:
Drill response (relation=<key>)
relation.items — not results. A node with no relations returns 200 with empty groups, not a 404.
Reading related results well
- Always pass
qon big entities. Unfiltered, a big entity returns hundreds of items (totalcaps at 250 withtotal_capped: true— render as “250+”).q=revenuenarrows and floats the right metrics (“Revenues”, “Revenue Per Employee”) to the top. Pagination ends at the cap — narrow withqto reach the tail. - Items are ranked by popularity, blended with match strength when
qis set — read the top few, not the tail. - Every item carries an
id— use it to hop (metric → its entities → their relations…) and to pin into search. - Enumerating a cohort (“Nvidia’s competitors”, “all NBA teams”, “the Magnificent Seven”): resolve the anchor entity → read its overview → pick the group by its
key(arel:*edge ormembers) → drill withlimit=100iftotalexceeds the preview. Members arrive as full nodes — feed them straight into per-member searches, no re-resolution. This replaces LLM-recalled member lists — the usual source of hallucinated comparisons — with a database read. Don’t treat a cappedsiblingsgroup as a cohort: it’s the class namespace, not curated peers.
Graph Node — confirm what an id refers to
GET /beta/graph/node/{id} resolves a single id (from a search card, or from the other graph endpoints) to its full name, aliases, and description. 404 when the id doesn’t resolve. You’ll rarely need it — mostly to verify that a card was built from the entity you meant before you rely on it.
Grounding /v3/search — two levers, use both
- Query text (wording drives retrieval). Compose queries from the resolved node’s name, aliases, and description — so a metric aliased “inflation” answers an inflation question even when the user said “CPI”. Keep queries short and data-shaped (subject + measure + time). Analytical/causal phrasing (“how has X affected Y”) retrieves nothing — collect the series, do the analysis in your synthesis step.
- Pinned node ids (deterministic candidacy). Pass resolved ids in the request body:
Use text alone when you resolved intent but not a specific node; add pinned ids whenever you did resolve the exact metric/entity and want to force it in.
Enforce grounding in code, not just the prompt — let your LLM write the wording, but guard what it emits:
"{node} {metric}" as the primary composer — it misses alias-named metrics and reads templated. Let the model compose from names + aliases; keep the concat as the fallback.
The agentic loop
The full pattern for a data-question agent:- Break a multi-part question into a few entities and/or metrics.
- Resolve each with Graph Search (
types=entityORtypes=metric; addlabelonly to force disambiguation, else let inference run). - Pick the node(s) — read
subtype,label, anddescription. - Explore their
q-filtered relations, ranked by popularity and match strength; keep the top few. For cohorts, drill the namedrel:*edge ormembers. - Compose grounded
/v3/searchqueries from the resolved names and aliases, and pin the resolved ids insources.data.node_ids. - Fetch concurrently; render each card’s
embed_urlas an iframe (embeds post their height via atako::resizemessage — see Embedding Knowledge Cards); report gaps.
- Models: question-breakdown and node-picking run fine on cheap/fast models; the compose step is the one that matters (wording drives hits) — give it your best model.
- Latency/cost: several graph round-trips + a couple of LLM calls fire before the data search, so the loop is slower than a single
/v3/search— but graph calls cost no credits and the LLM calls are small. The payoff is grounded searches that hit more often, plus an honest gaps report. - Caps are yours: a few entities, a few related metrics each, dedupe queries case-insensitively, bound total searches. Raise for research, lower for a chat sidebar.
Knowing what you don’t know
Report what the graph can’t ground as explicit gaps — “Tako has X and Y, but not Z.” An empty result is a valid, honest answer; never invent data to fill a gap. Saying what Tako knows and doesn’t is the whole advantage over blind search. Two caveats keep the report honest:- “Related” is table-level, not entity-level. A node’s related metrics are the metrics in the datasets that cover that entity — strong evidence, not proof of that exact combination.
/v3/searchis the final validator. - The graph is not the whole index. It indexes what’s structured as metrics/entities — not everything search can return. Stock/share-price and market-quote data especially is usually not a graph metric, yet search almost always has it (same for rankings, screens, overviews). A thin graph result for an entity Tako obviously covers is a cue to run an entity-level
/v3/search, not to declare a gap.
When to use — and when to decline
Use the graph-grounded loop for data/chart questions where Tako plausibly has coverage (public companies, macro indicators, indices, commodities, sports). Skip discovery when you already know the exact chart — a named chart or fully-qualified metric + entity goes straight toPOST /v3/search. Decline advice/opinion/prediction asks (“should I buy X”, “will Y happen”) — the pipeline fetches data, it doesn’t forecast; serve the factual sub-question and decline the rest. Poor fit: purely qualitative/sentiment asks, coding, trivia.
Common mistakes
Errors
Failures return{ "error_message": "...", "error_type": "..." }.