Overview#
defipipe is a historical DeFi data API serving point-in-time contract state: the result of calling a protocol's read functions at a specific block, reconstructed historically and indexed live. Every value is exact on-chain state, never an interpolation or a dashboard average.
The API is plain HTTPS + JSON.
https://api.defipipe.io/v1
Every series is named by one canonical series ID. Browse and copy IDs from the catalog; each dataset page shows its live series with ready-made queries.
Authentication#
Pass your API key as a bearer token, or in an x-api-key header. Keys look like dp_... and are managed from your dashboard.
curl "https://api.defipipe.io/v1/data?..." \ -H "Authorization: Bearer dp_your_key"
Requests without a key run on the free tier: 15m resolution and coarser over the trailing 90 days, for personal and evaluation use. See tiers for the full matrix.
Series IDs#
One canonical string identifies every series in storage, API calls, the SDK, and catalog URLs. String equality is identity equality.
{chain}:{protocol}:{version}:{instance}[/{scope}]:{kind}:{metric}| Segment | Meaning | Example |
|---|---|---|
| chain | Short chain slug | eth |
| protocol:version | Protocol template; also the catalog namespace | aave:v3 |
| instance | The contract address actually called (always the proxy) | 0x8787...4fa4e2 |
| /scope | Optional. The logical market inside a singleton contract (an Aave reserve, a Uniswap V4 pool). Instance-per-pool protocols have no scope. | /weth |
| kind | call (read-function result), storage (direct storage-slot read), or derived (computed from other series) | call |
| metric | For call: method[output], lowercased. For storage: a dotted field path. For derived: a slug. | getreservedata[currentvariableborrowrate] |
eth:aave:v3:0x87870bca3f3fd6335c3f4ce8392d69350b4fa4e2/weth:call:getreservedata[currentvariableborrowrate] eth:uniswap:v3:0x88e6a0c2ddd26feeb64f039a2c41296fcb3f5640:call:slot0[sqrtpricex96] eth:lido:v2:0xae7ab96520de3a18e5e111b5eaab095312d7fe84:call:totalsupply
IDs are normalized to lowercase. A non-lowercase ID is rejected as invalid, never coerced: the catalog displays ABI casing for readability, but every copy button hands you the canonical lowercase form. URL-encode IDs when they appear in a path segment (the / before a scope and the [] around outputs must be escaped).
GET/v1/data#
Observations for up to 10 series on one shared UTC time grid. Each value is the last observation at or before the grid boundary, resolved per block, never an average. No interpolation and no lookahead: what you get at each boundary is what the chain said at that moment, which makes the output safe for backtests.
| Param | Description |
|---|---|
| seriesstringrequired | Comma-separated series IDs, max 10. |
| freqenumrequired | block 1m 5m 15m 1h 4h 1d 7d. 1m/5m require Analyst, block requires Quant; block returns one row per sampled block with a top-level blocks grid. |
| from, toISO 8601required | The timeframe, e.g. from=2026-07-09&to=2026-07-12. |
| cursorstring | The next value from the previous page, verbatim. |
| limitint | Points per page, capped by your tier's page size. |
| fieldsstring | Per-series arrays to include: values,raw,blocks (the default). Trim to fields=valuesfor 2-3x lighter payloads when you don't need auditability on every call. Metadata (unit, decimals, note) always ships. |
curl "https://api.defipipe.io/v1/data?series=eth:lido:v2:0xae7ab96520de3a18e5e111b5eaab095312d7fe84:call:totalsupply&freq=1h&from=2026-07-09&to=2026-07-12"
{
"freq": "1h",
"tier": "free",
"index": ["2026-07-11T00:00:00.000Z", "2026-07-11T01:00:00.000Z"],
"next": null,
"series": {
"eth:lido:v2:0xae7ab96520de3a18e5e111b5eaab095312d7fe84:call:totalsupply": {
"kind": "call",
"unit": "steth",
"decimals": 18,
"values": [9177023.51929694, 9177032.49730232],
"raw": ["9177023519296935673858921", "9177032497302323064753489"],
"blocks": [25505640, 25505939],
"note": "values = raw / 10^18; raw is the exact on-chain integer"
}
}
}Every series returns three parallel arrays plus its scaling metadata: values holds unit-scaled floats, ready for analysis, scaled by the recipe in note; raw holds the exact on-chain integers as strings (uint256-safe, no float precision loss), so the float arithmetic is auditable: values = raw / 10^decimals(or the unit's recipe); blocks holds the source block behind each point, at every granularity.
Boundaries land on the UTC grid; the first boundary is the first grid point at or after from. A boundary with no observation at or before it returns null in all three arrays. Derived series (kind: "derived", e.g. AMM prices decoded from sqrtPriceX96) are computed at query time: they return raw: null and expose their formula (inputs + params) instead, so the computation is auditable via the raw values of the input series. Values newer than roughly 15 minutes may be revised in the rare event of a chain reorganization; everything older is final and never changes.
Pagination:windows larger than your tier's page size return a next cursor. Pass it back as cursor with the other params unchanged and repeat until next is null. The Python SDK does this transparently.
GET/v1/limits#
Machine-readable entitlements for whatever credential you present, so clients and agents can discover their limits up front instead of finding them through 429s.
curl https://api.defipipe.io/v1/limits -H "Authorization: Bearer dp_your_key"
{
"tier": "analyst",
"rate": { "sustained_per_min": 300, "burst": 1000 },
"freqs": ["1m", "5m", "15m", "1h", "4h", "1d", "7d"],
"history_days": 730,
"page_size": 10000,
"fields": ["values", "raw", "blocks"],
"license": "individual commercial",
"raw_values": "always included on /v1/data",
"max_active_keys": 3,
"docs": "https://defipipe.io/docs",
"upgrade": "https://defipipe.io/pricing"
}Rate limits#
Limits are token buckets keyed by API key (by IP on the free tier). The bucket holds a burst allowance well above the sustained rate and refills continuously at the sustained rate, because real workloads are bursty: a backtest pulling 50 series at startup should not need to trickle requests.
Every response carries x-ratelimit-limit, x-ratelimit-burst, and x-ratelimit-remaining. An empty bucket returns 429 with a Retry-After header and a JSON body:
{
"error": "rate limited",
"tier": "free",
"sustained_per_min": 30,
"burst": 100,
"retry_after_seconds": 2,
"upgrade": "/pricing"
}Tiers#
| Free ($0) | Analyst ($50/mo) | Quant ($200/mo) | |
|---|---|---|---|
| Granularity | 15m and coarser | 1m and coarser | block-level |
| History | Trailing 90d | Trailing 2y | Full, since deployment |
| Sustained rate | 30 rpm | 300 rpm | 1,000 rpm |
| Page size | 1,000 | 10,000 | 50,000 |
| License | personal and evaluation use | individual commercial | team; in-product redistribution with attribution |
| Active keys | 0 | 3 | 10 |
Response fields are not a paid feature: every tier returns the unit-scaled floats, the exact on-chain integers, and the source block of every point (all included by default; trim with fields=values). The paid axes are resolution, history depth, page size, and rate.
Plans and checkout live on the pricing page.
Errors#
Errors are JSON with an error message. Tier-gated responses include tier and an upgrade link.
| Status | Meaning |
|---|---|
| 400 | Malformed request: invalid series ID, unknown freq, bad timestamps, bad cursor. |
| 403 | Your tier does not cover the request (1m/5m without Analyst, block without Quant, history beyond your window, fields beyond your tier). |
| 404 | Unknown series ID: not in the registry. The error lists the unknown IDs. |
| 429 | Rate limited. Honor Retry-After. |
| 5xx | Our fault. Retry with backoff. |
Python SDK#
pip install defipipe
The SDK wraps /v1/data and returns pandas DataFrames, paging through cursors transparently. Set DEFIPIPE_API_KEY in your environment or pass api_key=.
from defipipe import Client # Multiple series on one time grid df = Client().data([ "eth:aave:v3:0x87870bca3f3fd6335c3f4ce8392d69350b4fa4e2/weth:call:getreservedata[currentvariableborrowrate]", "eth:lido:v2:0xae7ab96520de3a18e5e111b5eaab095312d7fe84:call:totalsupply", ], freq="1h", since="2026-07-09", until="2026-07-12") df.corr() df.attrs["units"] # unit/decimals recipe per series df.attrs["blocks"] # per-point source block per series
# Per-block rows with exact integers (API key required) raw = Client(api_key="dp_...").data( ["eth:uniswap:v3:0x88e6a0c2ddd26feeb64f039a2c41296fcb3f5640:call:slot0[sqrtpricex96]"], freq="block", since="2026-07-11", until="2026-07-12", raw=True, ) raw.head() # one row per block; values are exact Python ints (uint256-safe)
Missing a pool or market? Coverage is registry-driven: request it on the feature-request board and approved requests ship with full backfilled history.
MCP server#
Give any MCP-capable agent (Claude, Cursor, and others) direct access to the catalog and the data endpoint. The server is a thin wrapper over this API: same tiers, same limits, same series IDs. Pick your surface:
claude.ai and the Claude apps (no install)
- Open Settings, then Connectors, then Add custom connector.
- Name it defipipe and paste the URL below. Leave the OAuth fields empty: the free tier is keyless.
- Click Add, then Connect, then start a new chat.
- Ask something like: "chart the ETH price against the Aave WETH borrow rate for the last 24 hours".
https://mcp.defipipe.io/mcp
Claude Code
claude mcp add defipipe -- npx -y @defipipe/mcp
Claude Desktop, Cowork, and other local MCP clients
Add this under claude_desktop_config.json(Settings, Developer) or your client's MCP config; new sessions pick it up. The DEFIPIPE_API_KEY entry is optional and unlocks keyed tiers.
{
"mcpServers": {
"defipipe": {
"command": "npx",
"args": ["-y", "@defipipe/mcp"],
"env": { "DEFIPIPE_API_KEY": "dp_your_key" }
}
}
}Three tools: search_datasets, get_data, and get_limits. Without DEFIPIPE_API_KEY the server runs on the free tier; tier errors pass through with upgrade hints so agents can self-diagnose.