Quickstart

Go from zero to a live data call in under a minute. Your tengu_ key works across the FIRM data API (firm.tengu.co) and the Brain agent API (brain.tengu.co) — one key, one shared credit wallet.

Quick reference#

Base URLAuthBilling
FIRM data APIhttps://firm.tengu.coAuthorization: Bearer tengu_... (or X-API-Key)flat credits per call
Brain agent APIhttps://brain.tengu.co/v1Authorization: Bearer tengu_...credits per token

Both surfaces accept the same tengu_ key and debit the same credit wallet. Every FIRM response is a JSON envelope: { "ok": true, "timestamp": "<ISO-8601>", ...data }.

Using an AI agent or an SDK? Every tool is also reachable over the Model Context Protocol (Claude & other agents) and generated SDKs — same key, same wallet. See Connect via MCP & SDKs.

1. Get your key#

  1. In the dashboard, open Tengu API → Overview and reveal your key (it looks like tengu_...). Copy it once and store it like a password.
  2. Export it so the snippets below work:
Shell
export TENGU_API_KEY="tengu_..."

2. Authentication#

Both APIs accept the same key three ways, in this precedence (first one present wins):

  1. Authorization: Bearer tengu_...recommended; works unchanged with the OpenAI/Anthropic SDKs.
  2. X-API-Key: tengu_...
  3. ?api_key=tengu_... query parameter.

These three are equivalent:

Shell
1curl -H "Authorization: Bearer $TENGU_API_KEY" "https://firm.tengu.co/api/market/quote/AAPL"2curl -H "X-API-Key: $TENGU_API_KEY"            "https://firm.tengu.co/api/market/quote/AAPL"3curl "https://firm.tengu.co/api/market/quote/AAPL?api_key=$TENGU_API_KEY"

3. Your first data call (works on the Free plan)#

Pull a live quote — market_data, included in Free (1 credit/call):

Shell
1curl -H "Authorization: Bearer $TENGU_API_KEY" \2  "https://firm.tengu.co/api/market/quote/AAPL"
Python
1import os, requests2 3r = requests.get(4    "https://firm.tengu.co/api/market/quote/AAPL",5    headers={"Authorization": f"Bearer {os.environ['TENGU_API_KEY']}"},6    timeout=30,7)8r.raise_for_status()9data = r.json()              # envelope: {"ok": true, "quote": {...}}10print(data["quote"])

3b. Congressional trades (Starter and up)#

The congressional dataset is the one you can't get from a generic market-data API. It needs Starter or above (2 credits/call) — on Free this returns 402, which is the signal to upgrade, not a bug:

Shell
1curl -H "Authorization: Bearer $TENGU_API_KEY" \2  "https://firm.tengu.co/api/v3/intel/congress"
Python
1r = requests.get(2    "https://firm.tengu.co/api/v3/intel/congress",3    headers={"Authorization": f"Bearer {os.environ['TENGU_API_KEY']}"},4    timeout=30,5)6r.raise_for_status()7data = r.json()              # envelope: {"ok": true, "timestamp": ..., "items": [...], "count": N}8print(data["items"][:3])

Each call debits a flat per-product credit cost from your wallet (congressional = 2 credits). See your remaining balance any time on the Tengu API → Overview page. No failed request costs credits: any 4xx/5xx response ends up costing nothing (rejected before the meter, or charged at verification and refunded automatically), so a failed response always reports X-Credits-Cost: 0.

4. A few real endpoints to try#

All are GET unless noted. {ticker} is a symbol like AAPL. Every response is envelope-wrapped ({"ok": true, "timestamp": ..., ...}). Full list: API reference or GET /api/capabilities.

Shell
1# Live quote — market_data (1 credit) → {"ok":true, "quote": {...}}2curl -H "Authorization: Bearer $TENGU_API_KEY" "https://firm.tengu.co/api/market/quote/AAPL"3 4# SEC filings index — public_filings (1 credit) → {"ok":true, "ticker":"AAPL", "items":[...]}5curl -H "Authorization: Bearer $TENGU_API_KEY" \6  "https://firm.tengu.co/api/v3/fundamentals/sec_filings?ticker=AAPL&limit=10"7 8# 13F institutional holdings — funds (4 credits) → {"ok":true, "items":[...], "count":N}9curl -H "Authorization: Bearer $TENGU_API_KEY" "https://firm.tengu.co/api/v3/intel/sec13f?ticker=AAPL"10 11# Search private companies / investors / funds — FREE (0 credits, discovery) → {"ok":true, "results":[...]}12curl -H "Authorization: Bearer $TENGU_API_KEY" "https://firm.tengu.co/api/v3/private_markets/search?q=stripe"13 14# Full private-company profile — private_markets (5 credits, Pro+) — use an id from search above15curl -H "Authorization: Bearer $TENGU_API_KEY" "https://firm.tengu.co/api/v3/private_markets/company/{company_id}"

Free discovery, paid detail. /api/v3/private_markets/search is always free (it's the name→id typeahead). The paid detail (5 credits, Pro+) is /api/v3/private_markets/company/{company_id} and the other /api/v3/private_markets/* routes. Search first to get an id, then fetch the profile.

Which products your plan unlocks is in Pricing & credits. Calling a product your plan doesn't include returns 402 plan_required (no credits charged) — the body tells you which plan unlocks it. Upgrade in Tengu API → Plans.

5. Production-ready client (drop-in, with retries + error handling)#

This is the reference implementation — copy it as-is. It handles auth, the credit-cost-aware envelope, rate limits (honors Retry-After), plan/credit errors, and transient key-service outages.

Python
1import os, time, requests2 3FIRM_BASE = "https://firm.tengu.co"4KEY = os.environ["TENGU_API_KEY"]5SESSION = requests.Session()6SESSION.headers["Authorization"] = f"Bearer {KEY}"7 8class TenguError(RuntimeError):9    def __init__(self, status, code, message):10        super().__init__(f"[{status} {code}] {message}")11        self.status, self.code, self.message = status, code, message12 13def firm_call(path, params=None, method="GET", json_body=None, max_retries=4):14    """Call the FIRM data API. Returns the parsed JSON envelope, or raises TenguError.15    Retries only on 429 (rate limit) and 502 (key-service blip); never on 401/402/403/404."""16    url = f"{FIRM_BASE}{path}"17    for attempt in range(max_retries):18        resp = SESSION.request(method, url, params=params, json=json_body, timeout=30)19        if resp.status_code == 200:20            return resp.json()                      # {"ok": true, "timestamp": ..., ...data}21        # Error bodies are always {"detail": {"error_code": ..., "error_message": ...}}22        detail = (resp.json() or {}).get("detail", {}) if resp.content else {}23        code = detail.get("error_code", "unknown")24        msg = detail.get("error_message", resp.reason)25        if resp.status_code in (429, 502) and attempt < max_retries - 1:26            wait = int(resp.headers.get("Retry-After", 60 if resp.status_code == 429 else 5))27            time.sleep(min(wait * (2 ** attempt), 120))   # honor Retry-After, then back off28            continue29        # 401 (bad key), 402 (plan_required / usage_exceeded), 403, 404 → not retryable30        raise TenguError(resp.status_code, code, msg)31    raise TenguError(resp.status_code, code, msg)32 33# usage34trades = firm_call("/api/v3/intel/congress")["items"]35print(trades[:3])
TypeScript
1// TypeScript / Node (fetch). Same contract: Bearer auth, envelope responses, Retry-After backoff.2const FIRM_BASE = "https://firm.tengu.co";3const KEY = process.env.TENGU_API_KEY!;4 5export async function firmCall<T = any>(path: string, init: RequestInit = {}, maxRetries = 4): Promise<T> {6  const url = `${FIRM_BASE}${path}`;7  for (let attempt = 0; attempt < maxRetries; attempt++) {8    const resp = await fetch(url, { ...init, headers: { Authorization: `Bearer ${KEY}`, ...init.headers } });9    if (resp.ok) return (await resp.json()) as T;          // { ok: true, timestamp, ...data }10    const detail = (await resp.json().catch(() => ({}))).detail ?? {};11    if ((resp.status === 429 || resp.status === 502) && attempt < maxRetries - 1) {12      const wait = Number(resp.headers.get("Retry-After") ?? (resp.status === 429 ? 60 : 5));13      await new Promise((r) => setTimeout(r, Math.min(wait * 2 ** attempt, 120) * 1000));14      continue;15    }16    throw new Error(`[${resp.status} ${detail.error_code ?? "unknown"}] ${detail.error_message ?? resp.statusText}`);17  }18  throw new Error("exhausted retries");19}

6. Discover every endpoint (no key required)#

GET /api/capabilities returns the live manifest of the versioned data catalog — routes, methods, and params. Use it to plan calls before you write them:

Shell
1curl "https://firm.tengu.co/api/capabilities" | jq '{manifest_version, tool_count, groups}'2# tools[] entries carry: name, method, path, display_name, group (v2/v3), deprecated

7. The Brain agent API (Enterprise)#

Brain is an OpenAI/Anthropic-compatible agent that orchestrates FIRM tools for you — you write no tool-calling code. Brain requires the Enterprise plan (it has autonomous access to the full FIRM product suite). On any lower plan it returns 402 plan_required. Pro and below should call the FIRM data endpoints directly (§3–5).

Point your existing OpenAI SDK at https://brain.tengu.co/v1 and authenticate with the standard Authorization: Bearer header:

Python
1from openai import OpenAI2client = OpenAI(api_key=os.environ["TENGU_API_KEY"], base_url="https://brain.tengu.co/v1")3 4resp = client.chat.completions.create(5    model="brain",6    messages=[{"role": "user",7               "content": "What did Nancy Pelosi trade recently, and how did NVDA move around those dates?"}],8)9print(resp.choices[0].message.content)

That one request may internally pull congressional trades + market data — you orchestrate none of it. Brain is stateless per request (send the full messages[] history to continue a conversation), supports streaming (stream=true), and also exposes the Anthropic shape at POST /v1/messages. It debits the shared wallet by token (one metered event per turn, regardless of how many FIRM tools it called internally).

8. Errors you'll see#

Every error body is {"detail": {"error_code": "...", "error_message": "...", ...}}. None of these deduct credits.

Statuserror_codeMeaningWhat to do
401unauthorizedKey missing/invalid/disabled.Check the key; rotate under Tengu API → Overview.
402plan_requiredProduct not in your plan. Body adds tool_group, current_plan, unlocked_by[], upgrade_url.Upgrade to a plan in unlocked_by. No credits charged.
402usage_exceededWallet empty / overage cap reached.Top up or upgrade. No credits charged.
403account_suspendedBilling lapsed.Resolve billing in the dashboard.
404not_foundUnknown route or symbol.Check the path against GET /api/capabilities.
429rate_limitedPlan rate limit exceeded.Wait Retry-After seconds (60), then retry with backoff.
502unkey_unreachableKey service blip (transient).Retry after Retry-After seconds (5).

For Claude Code & AI agents#

If you're an agent setting this up for a user, this is all you need:

  • Config: TENGU_API_KEY from env (never hardcode). FIRM base https://firm.tengu.co; Brain base https://brain.tengu.co/v1. Auth: Authorization: Bearer $TENGU_API_KEY on both.
  • Discovery: GET https://firm.tengu.co/api/capabilities (no key needed) lists the live versioned data catalog with params — read it before generating calls instead of guessing paths. (A few legacy convenience routes like /api/market/quote/{ticker} shown above aren't in the manifest but remain supported.)
  • Responses: every FIRM response is {"ok": true, "timestamp": ..., ...data}. The data keys differ per endpoint (e.g. items for feeds, results for search, quote for a quote). Don't assume a fixed key — inspect the envelope.
  • Retry policy: retry ONLY 429 (wait Retry-After≈60s) and 502 (wait Retry-After≈5s), with exponential backoff capped at 120s. Do not retry 401/402/403/404 — surface them to the user (they need a key, an upgrade, or a fixed path).
  • Budget: each FIRM call costs a fixed number of credits by product (1–10; see Pricing & credits). search and /api/capabilities are free. Brain is metered per token, priced at 2,500 credits per $1 of model cost. A 402 plan_required body's unlocked_by[] tells the user exactly which plan to buy.
  • Ticker collisions — never present equity data as crypto. Nine symbols are BOTH a crypto asset and a US-listed equity: BTC, ETH, LINK, LTC, COMP, ARB, NEAR, APT, ATOM. On /api/v1/copilot/score/{ticker} and /api/v3/intel/ml_prediction/{ticker}, pass asset_class=equity or asset_class=crypto explicitly. A response for one of those nine carries a ticker_collision note — surface it to the user, don't strip it. See Ticker collisions below.
  • Use the §5 client verbatim — it already encodes the envelope, error shapes, and backoff correctly.

Ticker collisions: crypto vs equity#

Nine tickers are ambiguous — they name both a crypto asset and a US-listed equity:

BTC · ETH · LINK · LTC · COMP · ARB · NEAR · APT · ATOM

Scoring and prediction endpoints take an explicit asset_class parameter so the namespace is never guessed:

Shell
1# The US-listed EQUITY named BTC (not Bitcoin)2curl -H "X-API-Key: $TENGU_API_KEY" \3  "https://firm.tengu.co/api/v1/copilot/score/BTC?asset_class=equity"

The response carries an explicit disambiguation note:

JSON
1{2  "ticker_collision": {3    "note": "This is the US-listed equity 'BTC'. For the crypto asset, pass asset_class=crypto.",4    "crypto_available": false,5    "asset_class": "equity"6  }7}

Asking for the crypto asset fails closed rather than silently returning the equity:

Shell
1curl -H "X-API-Key: $TENGU_API_KEY" \2  "https://firm.tengu.co/api/v1/copilot/score/BTC?asset_class=crypto"3# 404  {"error": "crypto_model_unavailable", ...}

A 404 crypto_model_unavailable means the crypto model does not cover that asset — it does not mean "use the equity instead." Non-colliding tickers like AAPL are unaffected and carry no collision note.

For agents: treat ticker_collision as required context to relay. Presenting the equity BTC as Bitcoin is a correctness failure, not a formatting detail.

Next steps#

  • API reference — every endpoint, per-plan access, credit costs, and error shapes.
  • Pricing & credits — what each plan includes, add-ons, and what each call costs.
  • Chat parity — make Brain feel like Tengu Chat with optional context blocks.