> ## Documentation Index
> Fetch the complete documentation index at: https://docs.tako.com/llms.txt
> Use this file to discover all available pages before exploring further.

# tako-graph-agent — SKILL.md

This page hosts the installable [tako-graph-agent skill](/documentation/integrating-tako/data-graph/overview) for coding agents. The first fenced code block below is the complete `SKILL.md`. To install, save the block's contents (between the fences, exclusive) to `.claude/skills/tako-graph-agent/SKILL.md`, together with its companion [resolve-example.ts](/skills/tako-graph-agent/resolve-example) — or use the one-command install in [llms.txt](https://docs.tako.com/llms.txt).

The install command extracts everything between the first two lines that start with three backticks, and the rendered `.md` page serves the outer fence as three backticks. Code blocks inside the skill therefore use `~~~` fences; a backtick fence inside the block truncates the installed file.

```markdown theme={null}
---
name: tako-graph-agent
description: Use when building agentic flows over Tako's live financial, macro, and company data — resolving a name to a Data Graph node, finding what data Tako has for an entity or a metric, enumerating a cohort (competitors, index members, league teams), composing a grounded and pinned /v3/search query, or reporting what Tako does and does NOT have for a question. Also use when Tako searches return vague or irrelevant cards, or when you can't tell whether Tako has the data at all.
---

# Graph-grounded Tako search

Tako serves live financial, macro, and company data as embeddable
knowledge-card charts through `POST https://tako.com/api/v3/search`. A raw
search has one weakness: a miss looks the same as "no data." The Data Graph
endpoints (`GET /v1/graph/search`, `GET /v1/graph/related`) remove that
uncertainty by telling you what Tako has **before** you search. The pattern:

1. **Resolve** the question to real graph nodes.
2. **Explore** what those nodes relate to.
3. **Compose** `/v3/search` queries from the resolved names and aliases, and
   pin the resolved ids.
4. **Report** the gaps.

Adapt the pieces to the agent you're building. Two companions:

- The wire reference, with every parameter, response shape, and error. Fetch
  it when implementing:
  https://docs.tako.com/documentation/integrating-tako/data-graph/for-coding-agent.md
- `resolve-example.ts`, beside this file: the resolution loop, the compose
  guards, and the cohort loop, with why-comments. It imports the official SDK
  (`npm install tako-sdk`; Python: `pip install tako-sdk`). The SDKs expose
  `graphSearch` / `graph_search` and `graphRelated` / `graph_related`, and
  TypeScript camelCases parameters (`nodeId`).

**Access.** Base URL `https://tako.com/api`. Every endpoint, including the
graph ones, takes the key in the `X-API-Key` header (create one at https://tako.com/console/api-keys; a key
authenticates only on the host that issued it). An anonymous call returns
401. Graph calls consume no credits and are rate-limited to 180 requests per
minute and 10,000 per day per account; back off on 429 with jittered
exponential retry. Errors return `{"error_message": "...", "error_type": "..."}`.

> **Migrating from `/beta/graph`?** The endpoints moved to `/v1/graph/*`.
> `/beta/graph/*` returns identical responses until 2027-02-01, with
> `Deprecation` and `Sunset` headers. Three breaking changes: `subtype` is no
> longer a search **parameter** (sending it returns 400; use `label`, and read
> `subtype` as a field on every returned node); `relation_type=metric|entity`
> is deprecated in favor of `relation=metrics|entities` or a named key such as
> `rel:competes_with`; the overview response is an ordered `relations[]` list,
> not `related.metrics` / `related.entities`.

## Resolve: a thing or a measure

Decide per lookup whether you're resolving **a thing** (entity) or **a
measure** (metric), and pass that `types`. Don't mix them in one call.

`graph/search?q=<name>&types=entity` or `types=metric` (`limit` default 20,
max 50; `q` shorter than 2 characters returns empty `results`).

- **Pick the node by its fields.** Read each result's `subtype` (for example
  `Companies`, `Countries`), `label`, and `description`.
- **Disambiguate with `label`.** Tako infers labels from `q` by default and
  echoes them in `inferred_labels`. Pass `label=` only to force a reading:
  `q=air china` with `label=ORG` floats the airline above the country. A label
  is a ranking **boost, not a filter**: off-label nodes still return. Values:
  `PERSON`, `ORG`, `GPE`, `LOC`, `PRODUCT`, `EVENT`, `LANGUAGE`, `MONEY`,
  `METRIC`, `STOCK_TICKER`, `WEBSITE`. Sports teams are `ORG`.

## Explore: what a node relates to

`graph/related?node_id=<id>` with **no** `relation` returns the overview: an
ordered `relations[]` list of groups. Pick a group by its `key`, then drill it
with `relation=<key>` (`limit` default 50, max 100; `cursor` pages). Overview
items are in `relations[].items` and drill items in `relation.items`, **never
`results`**.

| `kind` | Keys | Meaning |
|---|---|---|
| `related` | `rel:<phrase>`, such as `rel:competes_with`, `rel:in_industry`, `rel:subsidiaries_of`, `rel:headquartered_in` | Named semantic edges. The richest signal for entities. |
| `membership` | `part_of`, `members` | Cohort and geographic membership (Apple `part_of` FAANG; a league's `members`). |
| `data` | `metrics`, `entities` | Table co-reference: metrics and entities that share datasets with this node. |
| `sibling` | `siblings` | Same-type peers. The class namespace ("Other Companies"), **not** a curated cohort. |
| `source` | `sources` | The data publishers whose datasets cover this node (for example S&P Global, Visible Alpha). |

- **Entity → its data:** `relation=metrics&q=<topic>`. **Always pass `q` on a
  big entity.** Unfiltered, `total` caps at 250 (`total_capped: true`; render
  it as "250+") and pagination stops at the cap, so `q` is the only way to
  reach the tail. Items rank by popularity, blended with match strength when
  `q` is set. Read the top few.
- **Metric → its entities:** `relation=entities`. Also popularity-ordered:
  *Revenues* → NVIDIA, Amazon, Microsoft; *Unemployment Rate* → the US, the
  UK, and so on.
- **Anchor → a cohort:** drill the named `rel:*` edge or `members` (add
  `limit=100` when `total` exceeds the preview). Members arrive as **full
  nodes**, so feed them straight into per-member searches with no
  re-resolution. This replaces an LLM-recalled member list, the usual source
  of hallucinated comparisons, with a database read. `siblings` is not a
  comparison set.
- **Keys are per-node.** Read the overview first; don't hardcode a `rel:*` key
  from a past run. An unknown key returns **200 with empty items**, so a typo
  reads as "no relation." Empty items are also the normal "no relations"
  outcome: contribute nothing and move on, don't retry.
- **Ids hop and pin.** Every item carries an `id`: use it to chain (metric →
  its entities → one entity's relations) and to pin into search. Ids are
  opaque and **not durable across graph rebuilds**: resolve at request time,
  don't store them. A stale id returns 404 from the graph endpoints; resolve
  the name again. `GET /v1/graph/node/{id}` confirms what an id refers to when
  you need to.

## Compose: two levers, use both

1. **Query text.** The LLM writes the query, grounded in the resolved names.
   Give it each 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 or
   causal phrasing ("how has X affected Y") retrieves nothing. Query the
   series; any analysis belongs in a later step, and only over values that
   step has read.
2. **Pinned node ids.** Pass the resolved ids under `sources.data.node_ids`
   (up to 20 distinct ids). A pinned node gets guaranteed retrieval candidacy
   and a strong boost; organic results are unaffected. A malformed id returns
   400; a stale id is skipped. `strict: true` returns only cards that match a
   pinned node and requires a non-empty `node_ids` (400 otherwise); if no card
   matches, or every pin is stale, the result is empty. Leave `strict` off
   when a partial answer beats none, and turn it on when an off-target card
   would be worse than no card.

~~~json
{
  "query": "Nvidia quarterly revenue",
  "sources": { "data": { "node_ids": ["<nvidia entity id>", "<revenues metric id>"] } }
}
~~~

**Enforce grounding in code, not only in the prompt** (implemented in
`resolve-example.ts`). Let the model write the wording, but guard what it
emits:

- Drop a composed query that doesn't cite a resolved name or alias verbatim.
- Keep one entity per query.
- If every query was dropped, fall back to mechanical
  `"{entity} {metric fragment}"` pairs. Don't use that concatenation as the
  *primary* composer: it misses alias-named metrics and reads templated.

**Entity-level searches** cover the broad case. When no specific metric fits
but a resolved entity is the right subject, search the entity itself (pinned)
and let `/v3/search` surface the cards the graph doesn't expose as nodes:
rankings, screens, overviews, prices.

## Report: know what you don't know

State the parts of the question the graph can't ground as explicit **gaps**:
*"Tako has X and Y for this, but not Z."* An empty result is a valid, honest
answer. Never invent data to fill a gap, and never write a synthesis
paragraph over cards whose values you haven't read: a card is an embed, not
a series, so a summary invites invented numbers. Cards are the answer; gaps
are the caveat. 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. That's why NVIDIA
  "has" *Average Wages*: shared tables. A listed metric is strong evidence,
  not proof. `/v3/search` is the final validator.
- **The graph isn't the whole index.** Stock, share-price, and market-quote
  data usually isn't a graph metric, yet search almost always has it. The same
  holds for rankings, screens, and overviews. A thin graph result for an
  entity Tako obviously covers means run an entity-level search, not declare
  a gap.

## Scope

Use the loop for data and chart questions where Tako plausibly has coverage:
public companies, macro indicators, indices, commodities, and sports.

- **Skip discovery** when you already know the exact chart: a named chart or a
  fully qualified metric + entity (+ timeframe) goes straight to `/v3/search`.
  A bare topic still goes through discovery.
- **Decline advice, opinion, and prediction** ("should I buy X", "will Y
  happen"). The pipeline fetches data; it doesn't judge or forecast. Serve the
  factual sub-question and decline the rest.
- **Poor fit:** purely qualitative or sentiment questions, coding, trivia, and
  prediction or betting markets.

## Building the pipeline

Break a multi-part question into a few entities and/or metrics, then run
resolve → explore → compose → report per part. Fetch searches concurrently and
render each card's `embed_url` as an iframe; embeds post their height through
a `tako::resize` message, so don't hard-code iframe heights. Any stage can
come up empty. That's a valid, visible outcome, not a failure.

- **Planner contract.** Give the planner a validated output shape so the graph
  phase runs deterministically: 1–3 different names for one subject
  ("Google", "Alphabet"), an optional `label` boost, and 2–5 short
  metric-name fragments for `related`'s substring `q`. The `Lookup` interface
  in `resolve-example.ts` is that contract.
- **Models.** Breakdown and node-picking run fine on cheap, fast models. The
  **compose step matters most**, because the wording drives whether a search
  hits. If you upgrade one call, upgrade that one.
- **Latency and cost.** Several graph round-trips and a couple of LLM calls
  run before any data search, so the loop is slower than one `/v3/search`. It
  stays cheap: graph calls consume no credits, and most LLM calls are small.
  The payoff is searches that hit more often and an honest gaps report.
- **Caps are yours to set.** Start with a few entities, a few related metrics
  per entity, case-insensitive query dedupe, and a bound on total searches.
  Raise them for research breadth; lower them for a chat sidebar.

## Red flags

Each of these means stop and reread the section above it:

- `types=entity,metric` in one lookup, or `label` used as a filter.
- `graph/related` on a big entity without `q`.
- A `rel:*` key typed from memory, or items read from `results[]`.
- `siblings` used as a comparison set, or an LLM-recalled member list.
- A stored node id.
- A text query with no pinned ids after you resolved a node.
- `strict: true` with ids you didn't resolve in this request: stale pins
  return an empty result.
- `"{node} {metric}"` concatenation as the primary composer.
- A causal or analytical query.
- A gap declared before an entity-level search, or a listed metric treated as
  proof of entity coverage.
- Empty relation items treated as an error and retried.
- A synthesis paragraph over cards whose values you haven't read, or no gaps
  output at all.
```


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.