Documentation

API reference

API operational

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.

Base URL
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.

Two query shapes cover most work: /v1/aligned returns multiple series resampled onto one shared time axis (right for analysis and backtests), and /v1/series/:id returns raw per-block rows for a single series (right when you need every observation and the exact uint256 value).

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.

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

Grammar
{chain}:{protocol}:{version}:{instance}[/{scope}]:{kind}:{metric}
SegmentMeaningExample
chainShort chain slugeth
protocol:versionProtocol template; also the catalog namespaceaave:v3
instanceThe contract address actually called (always the proxy)0x8787...4fa4e2
/scopeOptional. The logical market inside a singleton contract (an Aave reserve, a Uniswap V4 pool). Instance-per-pool protocols have no scope./weth
kindcall (read-function result), storage (direct storage-slot read), or derived (computed from other series)call
metricFor call: method[output], lowercased. For storage: a dotted field path. For derived: a slug.getreservedata[currentvariableborrowrate]
Examples
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.

ParamDescription
seriesstringrequiredComma-separated series IDs, max 10.
freqenum1m 5m 15m 1h 4h 1d 7d. Default 1h. 1m requires an API key.
from, toISO 8601Defaults: to = now, from = to minus 7 days.
Request
curl "https://api.defipipe.io/v1/aligned?series=eth:lido:v2:0xae7ab96520de3a18e5e111b5eaab095312d7fe84:call:gettotalpooledether&freq=1h"
Response 200
{
  "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.

ParamDescription
from, toISO 8601Defaults: to = now, from = to minus 24 hours.
after_blockintReturn only rows with a block number strictly greater. Pagination cursor.
limitintRows per page. Default 1,000, max 10,000.
Request
curl "https://api.defipipe.io/v1/series/eth:aave:v3:0x87870bca3f3fd6335c3f4ce8392d69350b4fa4e2%2Fweth:call:getreservedata%5Bcurrentvariableborrowrate%5D?limit=2" \
  -H "Authorization: Bearer dp_your_key"
Response 200
{
  "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.

Request
curl https://api.defipipe.io/v1/limits -H "Authorization: Bearer dp_your_key"
Response 200
{
  "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:

Response 429
{
  "error": "rate limited",
  "tier": "free",
  "sustained_per_min": 30,
  "burst": 100,
  "retry_after_seconds": 2,
  "upgrade": "/pricing"
}

Tiers#

FreeAnalystQuant
Sustained rate30 rpm300 rpm1,200 rpm
Burst1001,0005,000
Aligned freqs5m and coarserAll, incl. 1mAll, incl. 1m
Aligned historyTrailing 90dFullFull
Raw per-block rowsNoneHot window (~90d)Full history
Active keys0310

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.

StatusMeaning
400Malformed request: invalid series ID, unknown freq, bad timestamps, window too large.
403Your tier does not cover the request (raw rows without a key, 1m on free, history beyond 90d on free).
429Rate limited. Honor Retry-After.
5xxOur fault. Retry with backoff.

Python SDK#

Terminal
pip install defipipe

The SDK wraps both query shapes and returns pandas DataFrames. Set DEFIPIPE_API_KEY in your environment or pass api_key=.

Python
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()
Python
# 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.