Overview#
defipipe serves 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/series/... \ -H "Authorization: Bearer dp_your_key"
Requests without a key run on the free tier: aligned queries at 5m resolution and coarser over the trailing 90 days, no raw rows. 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:gettotalpooledether
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/aligned#
Resamples up to 10 series onto one shared UTC time axis. Each value is the last observation at or before the interval boundary, resolved per block. 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. |
| freqenum | 1m 5m 15m 1h 4h 1d 7d. Default 1h. 1m requires an API key. |
| from, toISO 8601 | Defaults: to = now, from = to minus 7 days. |
curl "https://api.defipipe.io/v1/aligned?series=eth:lido:v2:0xae7ab96520de3a18e5e111b5eaab095312d7fe84:call:gettotalpooledether&freq=1h"
{
"freq": "1h",
"tier": "free",
"index": ["2026-07-02T00:00:00.000Z", "2026-07-02T01:00:00.000Z", ...],
"series": {
"eth:lido:v2:0xae7ab96520de3a18e5e111b5eaab095312d7fe84:call:gettotalpooledether":
[9483211.4, 9483305.1, ...]
}
}Boundaries land on the UTC grid; the first boundary is the first grid point at or after from. A window can span at most 5,000 points per request: coarsen freq or narrow the range past that. A boundary with no observation at or before it returns null. Values are value_decimal, the unit-scaled float; when you need the exact raw integer, use /v1/series/:id.
GET/v1/series/:id#
Raw per-block rows for one series. Requires an API key: Analyst keys read the hot window (trailing ~90 days), Quant keys read full history.
| Param | Description |
|---|---|
| from, toISO 8601 | Defaults: to = now, from = to minus 24 hours. |
| after_blockint | Return only rows with a block number strictly greater. Pagination cursor. |
| limitint | Rows per page. Default 1,000, max 10,000. |
curl "https://api.defipipe.io/v1/series/eth:aave:v3:0x87870bca3f3fd6335c3f4ce8392d69350b4fa4e2%2Fweth:call:getreservedata%5Bcurrentvariableborrowrate%5D?limit=2" \ -H "Authorization: Bearer dp_your_key"
{
"id": "eth:aave:v3:0x87870bca...4fa4e2/weth:call:getreservedata[currentvariableborrowrate]",
"tier": "quant",
"rows": [
[22999871, 1751897135, "25113498247103484029703577", 2.5113498247103484e25],
[22999872, 1751897147, "25113498247103484029703577", 2.5113498247103484e25]
],
"next_after_block": 22999872
}Each row is [block_number, block_timestamp, value_raw, value_decimal]. value_raw is the exact on-chain value as a string (uint256-safe); value_decimal is the unit-scaled float. When a page fills, next_after_block is set: pass it back as after_block to continue. The last page returns null.
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"],
"raw_access": "hot",
"max_window_days": null,
"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 | Analyst | Quant | |
|---|---|---|---|
| Sustained rate | 30 rpm | 300 rpm | 1,200 rpm |
| Burst | 100 | 1,000 | 5,000 |
| Aligned freqs | 5m and coarser | All, incl. 1m | All, incl. 1m |
| Aligned history | Trailing 90d | Full | Full |
| Raw per-block rows | None | Hot window (~90d) | Full history |
| Active keys | 0 | 3 | 10 |
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, window too large. |
| 403 | Your tier does not cover the request (raw rows without a key, 1m on free, history beyond 90d on free). |
| 429 | Rate limited. Honor Retry-After. |
| 5xx | Our fault. Retry with backoff. |
Python SDK#
pip install defipipe
The SDK wraps both query shapes and returns pandas DataFrames. Set DEFIPIPE_API_KEY in your environment or pass api_key=.
from defipipe import Client # Aligned: multiple series on one time axis df = Client().aligned([ "eth:aave:v3:0x87870bca3f3fd6335c3f4ce8392d69350b4fa4e2/weth:call:getreservedata[currentvariableborrowrate]", "eth:lido:v2:0xae7ab96520de3a18e5e111b5eaab095312d7fe84:call:gettotalpooledether", ], freq="1h", since="2026-06-01") df.corr()
# Raw per-block rows (API key required) raw = Client(api_key="dp_...").series( "eth:uniswap:v3:0x88e6a0c2ddd26feeb64f039a2c41296fcb3f5640:call:slot0[sqrtpricex96]", since="2026-07-01", ) raw.head() # block_number -> ts, value_raw, value
Missing a pool or market? Coverage is registry-driven: request it on the feature-request board and approved requests ship with full backfilled history.