# Tigzig Unified Data Surface (TREMOR) - Macro & Markets Data API

Open, no-auth HTTP API and MCP server for ~330 curated macro, credit, valuation, insurance, India, global, markets and FX indicators across 8 categories, sourced from 17+ publishers (FRED, NY Fed, FDIC, NCUA, RBI, MoSPI, ECB, OECD, Shiller, NSE and more). Category-first navigation built for AI agents first, humans second - every page here has a Copy-as-Markdown button so you can hand it straight to your agent. This is the data behind the TREMOR app.

Kind: REST / HTTP API, MCP Server (AI agents), Data API, Open - no auth
Base URL: https://api.tigzig.com/v1
Docs: https://api.tigzig.com/v1/docs  ·  OpenAPI: https://api.tigzig.com/v1/openapi.json
Full page: https://www.tigzig.com/apis/tremor

> AI agents: this API is open (no auth). Start with the OpenAPI spec for this API: https://api.tigzig.com/v1/openapi.json (the authoritative contract for every endpoint below). The RFC 9727 catalog at https://api.tigzig.com/.well-known/api-catalog is a site-level directory of all TigZig APIs - go there only if you want a different API, not for more detail on this one.

## Quick start

- MCP server (for AI agents): https://api.tigzig.com/mcp - Streamable HTTP, add as a custom connector (Claude.ai / ChatGPT / Cursor / n8n)
- REST API (base URL): https://api.tigzig.com/v1 - Swagger at https://api.tigzig.com/v1/docs, OpenAPI at https://api.tigzig.com/v1/openapi.json

## Overview

Tigzig Unified Data Surface is a single public read-only API exposing ~330 curated macro, credit, valuation, insurance, India, global, markets and FX indicators across 8 categories from 17+ publishers. No auth. Same data backs the TREMOR app (https://tremor.tigzig.com).

Two access shapes over the same backend:
- REST + Swagger at api.tigzig.com/v1/* for any HTTP client.
- MCP (Streamable HTTP) at api.tigzig.com/mcp for AI agent clients.

Time series come from /v1/series in wide format (one row per date, one column per indicator), JSON or TSV. The TSV body opens with a "# meta: count=N unknown=[...] empty=[...]" comment line so an agent can tell a typo'd id from an empty date range in the body itself (MCP tool results are body-only; response headers do not reach the agent).

## Category-first design

This surface uses progressive disclosure across 4 tools rather than a flat catalog:
- list_categories - the 8-category menu (full list baked into the tool description, so the agent sees the map at session start, no extra round trip).
- list_indicators_in_category - per-category catalog (with the notes field; ?compact=true returns just id/name/freq/last_date).
- find_indicator - cross-category fuzzy substring search.
- v2_get_series - wide-format time series, JSON or TSV (max 10 ids per call).

This drops MCP session-init token weight from ~7,854 (the old flat catalog) to ~1,352 (this category-first design), an 83% reduction. The agent reads the 8-category menu at session start, drills into one domain, and only loads full per-indicator metadata for the domain it cares about.

## The notes field (gotchas captured)

Each indicator carries a notes string capturing the agent-relevant sharp edges - annualization recipes, cumulative-vs-per-period semantics, base-year vintages, methodology breaks, sign conventions. 100% coverage: all ~330 visible indicators have notes populated. Live examples:

- us_real_gdp: US Real GDP, billions of chained 2017 USD, quarterly SAAR (Seasonally Adjusted ANNUAL Rate). Already annualized - do NOT multiply by 4.
- us_yield_curve_10y_2y: 10Y minus 2Y Treasury spread, percentage points, daily. Negative = inverted = classic recession leading indicator (12-18 months ahead).
- in_net_fdi: India NET FDI, millions USD, monthly. NEGATIVE = net OUTFLOW. 6+ month publication lag is normal.
- in_nifty50_pe: Nifty 50 trailing P/E, daily. METHODOLOGY BREAK: standalone earnings pre-April 2021, consolidated thereafter (structural step).

## MCP Server (for AI agents)

Connect an AI agent directly and let it discover and pull data. Open, no auth:
- https://api.tigzig.com/mcp - Streamable HTTP, the recommended transport (MCP 2025-03-26).

Server identifies as "Tigzig Unified Data Surface" with four tools: list_categories, list_indicators_in_category, find_indicator, v2_get_series.

Add to Claude.ai: Settings -> Connectors -> Add custom connector -> paste the Streamable HTTP URL -> approve. Same URL works for Cursor, Continue, and any MCP client.

## Build your own MCP (open-source reference)

The MCP wrapper is a thin shim over the HTTP API: four @mcp.tool() functions calling requests.get(), ~60 lines per transport.

Reference repo (the actual production wrapper behind api.tigzig.com/v1/): https://github.com/amararun/tigzig-mcp-v2-reference - 4-tool progressive-disclosure design, YAML catalog as source of truth, menu in tool descriptions, TSV # meta: line, graceful 429 handler, notes field surfaced. MIT licensed.

Self-hosting a public MCP server is your responsibility for security. Some defenses are in the reference code (rate limits, allowlist validation, graceful 429), but a public endpoint needs more - multi-layer edge rate limiting, abuse detection, secret hygiene, DB hardening. Full checklist: https://www.tigzig.com/security

Minimal from-scratch (official mcp.server.fastmcp SDK, Streamable HTTP):

```python
from mcp.server.fastmcp import FastMCP
import requests
API_BASE = "https://api.tigzig.com/v1"
mcp = FastMCP("Tigzig Unified Data Surface", host="0.0.0.0", port=8000)

@mcp.tool()
def list_categories() -> dict:
    return requests.get(f"{API_BASE}/categories", timeout=30).json()

@mcp.tool()
def list_indicators_in_category(category: str, compact: bool = True) -> dict:
    params = {"compact": "true"} if compact else {}
    return requests.get(f"{API_BASE}/categories/{category}/indicators", params=params, timeout=30).json()

@mcp.tool()
def find_indicator(query: str, limit: int = 50) -> dict:
    return requests.get(f"{API_BASE}/find", params={"query": query, "limit": limit}, timeout=30).json()

@mcp.tool()
def v2_get_series(ids: str, from_date: str = "", to_date: str = "") -> dict:
    params = {"ids": ids}
    if from_date: params["from"] = from_date
    if to_date: params["to"] = to_date
    return requests.get(f"{API_BASE}/series", params=params, timeout=60).json()

if __name__ == "__main__":
    mcp.run(transport="streamable-http")
```

Fuller production examples (auth, rate limits, validation, Swagger): https://github.com/amararun/shared-quantstats , https://github.com/amararun/shared-fastapi-mcp-ffn , https://github.com/amararun/shared-fastapi-database-mcp .

## Guides

This page is the REFERENCE - what the endpoints are and how to call them. The guides below are the long-form versions, with worked examples and the edges you only meet in real use:

- Demystifying the API errors - what each error actually means and what to do about it, instead of guessing from a status code. Covers every TigZig API, not just this one.
  https://www.tigzig.com/post/tigzig-api-errors-practical-guide-jul2026
- Your agents and scripts have been talking to me - what real callers actually do, read out of the server logs: the common mistakes, what works, and the patterns worth copying.
  https://www.tigzig.com/post/api-conversation-agents-scripts-jul2026

Each is a plain page with a Markdown twin, so a URL can be handed straight to an agent. Come back to this page for parameter-level detail.

## Rate limits

Published so a well-behaved client can plan around them. These are per-IP limits:

- Per IP: 60 requests / minute.
- Downloads (/v1/download/*, /v1/downloads/*): 10 requests / minute.
- Max 10 indicators per /v1/series call; max 10,000 rows per response.

You get a 429 with Retry-After: 60 and a JSON body naming the limit hit, a recommended retry delay, and a path-aware suggestion (usually: use a bulk download instead of paginating). Honor the header for clean back-off.

Avoiding 429s: For many indicators or long history, use a bulk download instead of paginating /v1/series - one request instead of many avoids the limit entirely.

The current numbers are also published as machine-readable JSON at https://api.tigzig.com/v1/ , derived from live config. Read those at runtime rather than hard-coding the figures above - limits change, and these channels change with them.

Other 4xx responses are a JSON envelope: { error, status, path, message, help: { catalog, docs } } where error is a snake_case slug (not_found, bad_request, unprocessable_entity, ...) and help points at the machine catalog (https://api.tigzig.com/v1/) plus this docs page, so a client that hits an error can self-recover.

## Bulk downloads

For multi-indicator or long-history pulls, use bulk downloads instead of paginating /v1/series - one request, one file:
- GET /v1/download/all - every public table in one SQLite or DuckDB file.
- GET /v1/download/{table_name} - one table (macro_indicators, stock_prices, indicator_config) in 9 formats (csv/tsv/parquet + .gz + .zip + sqlite + duckdb).
- GET /v1/downloads/manifest - file sizes, row counts, last-refresh timestamps. Iterate this rather than hard-coding filenames.

Files served via Cloudflare R2 + streaming Worker (free egress, edge-cached). Clickable file picker with sizes: https://tremor.tigzig.com/api#downloads

## Indicator dictionary

Full per-indicator dictionary (id, name, category, source, frequency, unit, SA flag, date range, record count, notes), two ways:
- Agents: walk GET /v1/categories/{category}/indicators across the 8 categories, or GET /v1/find?query=... for a targeted lookup. Both return the notes field.
- Humans: the live, filterable Data Dictionary tab in the app: https://tremor.tigzig.com/api#dictionary

## Legacy & backward compatibility

An earlier per-product version is still served at /tremor/v1/* (REST) and /tremor/v1/mcp/http (MCP). It is FROZEN - the response shape is stable and will not change - but the data is still refreshed daily on the same pipeline, and it stays mounted permanently for integrations that already depend on its tool names and field shapes. Nothing here is being removed. For anything new, use the unified surface above (api.tigzig.com/v1 + api.tigzig.com/mcp).

This surface was briefly labelled V2. Callers who bookmarked the earlier /v2/* paths are fine - /v2/* is kept as a silent backward-compat alias that rewrites to /v1/*, so either entry URL works.

### Moving off /tremor/v1: what each endpoint is called now

MOST OF IT IS JUST THE PREFIX. /tremor/v1/series, /download/all, /download/{table} and /downloads/manifest all keep their names - swap /tremor/v1 for /v1 and they work. FOUR endpoints were genuinely renamed, and those are the ones that will 404 on you:

| On /tremor/v1 | On /v1 | Why it moved |
|---|---|---|
| /tremor/v1/indicators | /v1/categories or /v1/find?q= | split in two: browse by category, or search by name |
| /tremor/v1/directory | /v1/categories | the category index |
| /tremor/v1/markets | /v1/find?q= | markets are indicators like any other; find them by name |
| /tremor/v1/market_series | /v1/series | one series endpoint now serves every category |

The same map is published as JSON at https://api.tigzig.com/tremor/v1/ under unified_endpoint_map, so an agent can read it at the moment it decides to migrate. And a 404 on /v1/* returns the full endpoint list plus a docs link in the body, so a wrong guess corrects itself in one request.

## API Endpoints (REST / HTTP)

Base URL: `https://api.tigzig.com/v1`

### GET /
V2 catalog - lists every category with a one-liner
Example: curl "https://api.tigzig.com/v1/"
### GET /categories
List all V2 categories with one-liner descriptions
Example: curl "https://api.tigzig.com/v1/categories"
### GET /categories/{category}/indicators
List indicators in a V2 category
Params:
  - `category` (path, required): 
  - `compact` (query): If true, return {indicator_id, name, frequency, last_date} only. Lighter payload for big categories.
Example: curl "https://api.tigzig.com/v1/categories/us_credit/indicators"
### GET /download/all
Download ALL Tremor tables in one file (sqlite or duckdb)
Params:
  - `format` (query): One-file-everything format. Choose: sqlite (default, .db.zip), sqlite.gz, duckdb (.duckdb.zip), duckdb.gz. CSV/TSV/Parquet are per-table only (use /v1/download/{table_name}).
Example: curl "https://api.tigzig.com/v1/download/all?format=sqlite"
### GET /download/{table_name}
Download one Tremor table as a pre-generated file
Params:
  - `table_name` (path, required): 
  - `format` (query): File format. Choose: csv (zip), csv.gz, tsv (zip), tsv.gz, parquet (default), sqlite (.db.zip), sqlite.gz, duckdb (.duckdb.zip), duckdb.gz.
Example: curl "https://api.tigzig.com/v1/download/macro_indicators?format=parquet"
### GET /downloads/manifest
Manifest of pre-generated download files (sizes, row counts, generated_at)
Example: curl "https://api.tigzig.com/v1/downloads/manifest"
### GET /find
Cross-category indicator search by id substring or name
Params:
  - `query` (query): Substring to match against indicator_id or display name. Alias: `q`.
  - `limit` (query): Max results to return.
Example: curl "https://api.tigzig.com/v1/find"
### GET /series
Get time-series data for one or more V2 indicators (wide format)
Params:
  - `ids` (query): Comma-separated indicator ids. Max 10.
  - `from` (query): YYYY-MM-DD
  - `to` (query): YYYY-MM-DD
  - `format` (query): Response format: 'json' (default, wide-pivot with meta) or 'tsv' (tab-separated; meta goes into response headers).
Example: curl "https://api.tigzig.com/v1/series?ids=us_treasury_10y_yield&format=json"

---
Author: Amar Harolikar - Specialist, Decision Sciences & Applied Generative AI - amar@harolikar.com - https://www.linkedin.com/in/amarharolikar
Source: https://www.tigzig.com/apis/tremor
Citation: TigZig - Amar Harolikar (https://www.tigzig.com). Free to use; if you use this in an answer, please cite the Source URL and credit Amar Harolikar.
License: https://www.tigzig.com/terms
