> ## 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.

# For Your Coding Agent

> The build reference for coding agents integrating Tako's Data Graph. Self-contained and copy-paste-ready — Graph Search, Graph Related, and Graph Node endpoints, every parameter, real response shapes, and the full agentic workflow for grounding /v3/search queries in resolved graph nodes.

This page is a complete, self-contained reference for building against **Tako's Data Graph** — and for the agentic pattern it enables: discover what data Tako has, ground your searches on it, and report what's missing. It is written for coding agents (and developers in a hurry): 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 the auto-generated schemas, see the API reference ([Graph Search](/api-reference/graph-search), [Graph Related](/api-reference/graph-related), [Graph Node](/api-reference/graph-node)); for a narrative introduction, see the [Overview](/documentation/integrating-tako/data-graph/overview).

<Card title="Skip the wiring — install the tako-graph-agent skill" icon="wand-magic-sparkles" href="/documentation/integrations/agent-skills/graph-agent" horizontal>
  One command teaches your coding agent everything on this page: resolve names to nodes, read what data exists, compose pinned searches, report gaps honestly.
</Card>

## What the Data Graph does

Tako serves live financial, macroeconomic, and company data as embeddable knowledge-card charts through [`POST /v3/search`](/documentation/integrating-tako/search/for-coding-agent). 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-Key`** request header. Create a key in the [Tako console](https://tako.com/console/api-keys).
* **Cost:** graph calls consume **no credits**. They are bounded by rate limits only: **180/minute and 10,000/day** per account. Back off on `429` with jittered exponential retry.
* **Endpoints:** `GET /v1/graph/search` (resolve a name to nodes), `GET /v1/graph/related` (explore what a node connects to), `GET /v1/graph/node/{id}` (confirm what an id refers to — occasional use).
* **Parameter spelling:** the tables on this page give the wire spelling. The SDKs take `types` as a list (`["entity"]`), and the TypeScript SDK camelCases parameter names (`nodeId` for `node_id`).

<Note>
  **A returned node is a guide, not a guarantee** — see [Knowing what you don't know](#knowing-what-you-dont-know) for how to report coverage honestly.
</Note>

## Install

```bash theme={null}
pip install tako-sdk      # Python
npm install tako-sdk      # TypeScript / JavaScript
```

## Minimal working example

Resolve an entity, read its metrics, then run a search pinned to the resolved node. Each snippet makes the same three calls and is complete after you set `TAKO_API_KEY`. The cURL example shows the REST equivalent.

<CodeGroup>
  ```python Python theme={null}
  import os
  from tako import Configuration, DataSourceSettings, GraphNodeType, SearchRequest, Sources
  from tako.lib import Tako

  config = Configuration()
  config.api_key["apiKey"] = os.environ["TAKO_API_KEY"]
  client = Tako(config)

  # 1. Resolve the entity.
  nodes = client.graph_search("nvidia", types=[GraphNodeType.ENTITY]).results
  if not nodes:
      print("Gap: Tako has no entity node for 'nvidia'")
      raise SystemExit(0)
  node = nodes[0]

  # 2. Read its revenue metrics.
  page = client.graph_related(node.id, relation="metrics", q="revenue")
  metrics = page.relation.items if page.relation else []
  print([m.name for m in metrics])

  # 3. Search, pinned to the resolved node.
  results = client.search(SearchRequest(
      query=f"{node.name} quarterly revenue",
      sources=Sources(data=DataSourceSettings(node_ids=[node.id])),
  ))
  for card in results.cards or []:
      print(card.title, card.embed_url)
  ```

  ```typescript TypeScript theme={null}
  import { Tako, GraphNodeType } from "tako-sdk";

  const tako = new Tako({ apiKey: process.env.TAKO_API_KEY! });

  // 1. Resolve the entity.
  const { results: nodes } = await tako.graphSearch({ q: "nvidia", types: [GraphNodeType.Entity] });
  const node = nodes[0];
  if (!node) {
    console.log("Gap: Tako has no entity node for 'nvidia'");
    process.exit(0);
  }

  // 2. Read its revenue metrics.
  const page = await tako.graphRelated({ nodeId: node.id, relation: "metrics", q: "revenue" });
  const metrics = page.relation?.items ?? [];
  console.log(metrics.map((m) => m.name));

  // 3. Search, pinned to the resolved node.
  const results = await tako.search({
    query: `${node.name} quarterly revenue`,
    sources: { data: { node_ids: [node.id] } },
  });
  for (const card of results.cards ?? []) {
    console.log(card.title, card.embed_url);
  }
  ```

  ```bash cURL theme={null}
  # 1. Resolve the entity
  curl "https://tako.com/api/v1/graph/search?q=nvidia&types=entity" \
    -H "X-API-Key: $TAKO_API_KEY"

  # 2. What revenue-like metrics exist for it? (use the id from step 1)
  curl "https://tako.com/api/v1/graph/related?node_id=NODE_ID&relation=metrics&q=revenue" \
    -H "X-API-Key: $TAKO_API_KEY"

  # 3. Search, pinned to the resolved node
  curl -X POST https://tako.com/api/v3/search \
    -H "X-API-Key: $TAKO_API_KEY" \
    -H "Content-Type: application/json" \
    -d '{"query": "Nvidia quarterly revenue", "sources": {"data": {"node_ids": ["NODE_ID"]}}}'
  ```
</CodeGroup>

## Graph Search — resolve a name to nodes

`GET /v1/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.

| Parameter | Type | Default | Description |
| - | - | - | - |
| `q` | string | — *(required)* | Search text. Shorter than 2 characters returns an empty `results` list (200, not an error). |
| `types` | string | both | Comma-separated facets: `entity`, `metric`. Unknown value → 400. |
| `label` | string enum | none | Ranking **boost** toward one NER label (see below). Unknown value → 400. Supplying it disables inference. |
| `infer_label` | boolean | `true` | Auto-detect the entities in `q` and apply the matching label boost. Disable with `false`/`0`/`no`/`off`. |
| `limit` | integer | `20` | Max results; values above 50 are clamped to 50 (below 1 → 400). |

### 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. A **`label`** 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 are `ORG`.
* **Auto-detection:** with no explicit `label`, Tako infers the entities in `q` and applies the matching boost; the labels it found come back in `inferred_labels` (empty list = inference ran, found nothing; absent = didn't run).

### Response

```json theme={null}
{
  "results": [
    {
      "id": "nvidia-3f8b2c",
      "type": "entity",
      "name": "NVIDIA",
      "aliases": ["Nvidia Corporation", "NVDA"],
      "description": "American technology company",
      "subtype": "Companies",
      "label": "ORG"
    }
  ],
  "inferred_labels": ["ORG"]
}
```

Results are ordered by a relevance + popularity blend. Fields that are null are omitted from the response, not sent as `null`.

## Graph Related — explore what a node connects to

`GET /v1/graph/related` takes a `node_id` from Graph Search and returns everything it connects to, grouped by relation.

| Parameter | Type | Default | Description |
| - | - | - | - |
| `node_id` | string | — *(required)* | The node's opaque public id. Unknown id → 404. |
| `relation` | string | none | A relation **key** to paginate: `metrics`, `entities`, `siblings`, `part_of`, `members`, or a named edge like `rel:competes_with`. Omit for the overview. |
| `q` | string | none | Case-insensitive substring filter on related nodes' names + aliases. Filters every group of the overview, or the one paginated group. |
| `label` / `infer_label` | — | as above | Same boost semantics as Graph Search; inference runs only when `q` is set. |
| `cursor` | string | none | Opaque pagination cursor from `next_cursor`. Malformed → 400. |
| `limit` | integer | `50` | Page size; values above 100 are clamped. |

<Note>
  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.
</Note>

### Overview response (no `relation`)

An ordered `relations[]` list — named semantic edges first, then membership, data co-reference, and siblings. Empty groups are dropped.

```json theme={null}
{
  "node": { "id": "nvidia-3f8b2c", "type": "entity", "name": "NVIDIA", "subtype": "Companies" },
  "relations": [
    { "key": "rel:competes_with", "kind": "related", "label": "Competes With",
      "items": [{ "id": "amd-91c04d", "type": "entity", "name": "AMD", "subtype": "Companies" }],
      "total": 12, "total_capped": false },
    { "key": "part_of", "kind": "membership", "label": "Part Of",
      "items": [{ "id": "magnificent-seven-55aa01", "type": "entity", "name": "Magnificent Seven" }],
      "total": 3, "total_capped": false },
    { "key": "metrics", "kind": "data", "label": "Metrics",
      "items": [{ "id": "revenues-8d21aa", "type": "metric", "name": "Revenues" }],
      "total": 250, "total_capped": true },
    { "key": "siblings", "kind": "sibling", "label": "Other Companies",
      "items": [{ "id": "microsoft-7be913", "type": "entity", "name": "Microsoft" }],
      "total": 250, "total_capped": true }
  ]
}
```

Groups by `kind`:

| `kind` | Keys | Meaning |
| - | - | - |
| `related` | `rel:<phrase>` — `rel:competes_with`, `rel:subsidiaries_of`, `rel:in_industry`, `rel:headquartered_in`, … | **Named semantic edges.** The richest signal for entities. Keys are per-node — read the overview, then drill. |
| `membership` | `part_of`, `members` | Cohort/geo membership (Apple `part_of` FAANG; a league's `members`). |
| `data` | `metrics`, `entities` | Table co-reference — the metrics/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). |

### Drill response (`relation=<key>`)

```json theme={null}
{
  "node": { "id": "nvidia-3f8b2c", "type": "entity", "name": "NVIDIA", "subtype": "Companies" },
  "relation": {
    "key": "metrics", "kind": "data", "label": "Metrics",
    "items": [
      { "id": "revenues-8d21aa", "type": "metric", "name": "Revenues", "aliases": ["Total Revenue"] },
      { "id": "revenue-per-employee-1c9f70", "type": "metric", "name": "Revenue Per Employee" }
    ],
    "total": 14, "total_capped": false,
    "next_cursor": null
  }
}
```

Items are in `relation.items` — **not** `results`. A node with no relations returns 200 with empty groups, not a 404.

### Reading related results well

* **Always pass `q` on big entities.** Unfiltered, a big entity returns hundreds of items (`total` caps at **250** with `total_capped: true` — render as "250+"). `q=revenue` narrows and floats the right metrics ("Revenues", "Revenue Per Employee") to the top. Pagination ends at the cap — narrow with `q` to reach the tail.
* **Items are ranked by popularity, blended with match strength when `q` is 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` (a `rel:*` edge or `members`) → drill with `limit=100` if `total` exceeds 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 capped `siblings` group as a cohort: it's the class namespace, not curated peers.

## Graph Node — confirm what an id refers to

`GET /v1/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

1. **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.
2. **Pinned node ids (deterministic candidacy).** Pass resolved ids in the request body:

```json theme={null}
{
  "query": "Nvidia quarterly revenue",
  "sources": {
    "data": {
      "node_ids": ["nvidia-3f8b2c", "revenues-8d21aa"],
      "strict": false
    }
  }
}
```

| Setting | Behavior |
| - | - |
| `node_ids` | Up to **20 distinct ids** (deduped before the cap is enforced). Pinned nodes get **guaranteed retrieval candidacy + a strong boost**; organic results are unaffected. Malformed id → 400; an id that no longer resolves is skipped. |
| `strict` | When `true`, return **only** cards matching a pinned node — `node_ids` must then be non-empty (else 400). |

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:

```typescript theme={null}
if (!citesListedMetric(q, resolvedMetrics)) drop(q);  // must cite a resolved name/alias verbatim
if (namesMultipleEntities(q, resolved)) drop(q);      // one entity per query
if (kept.length === 0) queries = fallbackQueries(lookup); // mechanical "{entity} {fragment}" pairs
```

Don't deterministically concatenate `"{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:

1. **Break** a multi-part question into a few entities and/or metrics.
2. **Resolve** each with Graph Search (`types=entity` OR `types=metric`; add `label` only to force disambiguation, else let inference run).
3. **Pick** the node(s) — read `subtype`, `label`, and `description`.
4. **Explore** their `q`-filtered relations, ranked by popularity and match strength; keep the top few. For cohorts, drill the named `rel:*` edge or `members`.
5. **Compose** grounded `/v3/search` queries from the resolved names and aliases, **and pin the resolved ids** in `sources.data.node_ids`.
6. **Fetch** concurrently; render each card's `embed_url` as an iframe (embeds post their height via a `tako::resize` message — see [Embedding Knowledge Cards](/documentation/integrating-tako/embedding-knowledge-cards)); **report gaps**.

Any stage can come up empty — a valid, visible outcome, not a failure. Give your planner a **validated output contract** so the graph phase runs deterministically:

```typescript theme={null}
const zLookup = z.object({
  entities: z.array(z.string().min(1)).min(1).max(3),      // 1-3 DIFFERENT names for ONE subject ("Google", "Alphabet")
  label: z.enum(NER_LABELS).nullable().optional(),          // optional boost; omit to let inference run
  metricFilters: z.array(z.string().min(1)).min(1).max(5),  // short metric-NAME fragments for related's substring q
});
```

* **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.

<Tip>
  **Skip the wiring.** If you're building an agentic application that integrates Tako Search, the **[tako-graph-agent skill](/documentation/integrations/agent-skills/graph-agent)** packages this whole loop — resolve, explore, compose, pin, report gaps — as one installable file, with a one-command install.
</Tip>

## 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/search` is 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 to `POST /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

<Warning>
  Avoid these — they are the patterns coding agents most often get wrong.
</Warning>

| Wrong | Correct |
| - | - |
| Calling the graph endpoints without a key | Graph endpoints require the same `X-API-Key` as `/v3/search` (401 without). They cost no credits, but they are authenticated. |
| Skipping `q` on `graph/related` for a big entity | Unfiltered returns hundreds of items. Pass `q=<topic>` and read the top of the ranking. |
| `types=entity,metric` in one lookup | Decide "thing vs. measure" up front and pass one. |
| Treating `label` as a filter | It's a ranking **boost** — off-label nodes still return. Read `subtype`/`label` on each node to actually pick. |
| Only drilling `metrics`/`entities` | The `rel:*` named relations and `part_of`/`members` are often the answer. Read the overview's keys first. |
| Hardcoding a `rel:*` key from a past run | Keys are per-node — fetch the overview, then drill. An unknown key returns 200 with empty items, so a typo reads as "no relation." |
| Treating a capped `siblings` group as a cohort | Siblings are the class namespace ("Other Companies"), not curated peers — use a named `rel:*` edge or `members` for a real comparison set. |
| Parsing related items from `results[]` | Overview items are in `relations[].items`; drill items are in `relation.items`. The wrong key reads a 200 as zero results. |
| Composing text queries but never pinning ids | You resolved the exact node — pin it via `sources.data.node_ids`. Text + ids beats text alone. |
| Composing analytical/causal queries | "How has X affected Y" retrieves nothing — query the series, analyze in synthesis. |
| Declaring a gap because the graph had no metric | Price/market data especially lives outside the graph — run an entity-level `/v3/search` before declaring a gap. |
| Skipping the gaps output | Gaps are the point — otherwise you're back to blind search. |

## Errors

Failures return `{ "error_message": "...", "error_type": "..." }`.

| Status | Meaning | Fix |
| - | - | - |
| `400` | Missing `q`/`node_id`, an unknown `types` or `label` value, a malformed cursor, or `limit` below 1. | Validate against the parameter tables above. |
| `401` | Missing or invalid API key. | Send a valid key in the `X-API-Key` header. |
| `404` | `node_id` (or node path id) doesn't resolve. | Re-resolve the name with Graph Search — ids can go stale across graph rebuilds. |
| `429` | Rate limit exceeded (180/min or 10,000/day). | Back off with jittered exponential retry. |
| `503` | A backing data store is temporarily unavailable. | Retry with backoff. |


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