API Documentation
The pigi.finance Data API (v1) gives programmatic access to the same DeFi vault data that powers this site — current metrics, daily history, aggregated stats, benchmark rates, and hack/loss history. All responses are JSON and CORS is open, so you can call it from a server or a browser.
Need a key? Request access on the API page. You'll receive a key like pigi_….
Machine-readable spec: OpenAPI 3 (openapi.yaml) — import it into Postman, Swagger UI, or your codegen of choice.
Authentication
Auth is a two-step API key → token exchange. Your long-lived API key is secret; you trade it for a short-lived JWT (≈1 hour) and send that as a Bearer token on every request. When the token expires, exchange the key again.
# 1. Exchange your API key for a short-lived token
curl -X POST https://pigi.finance/api/v1/auth/token \
-H "Content-Type: application/json" \
-d '{"apiKey":"pigi_live_xxxxxxxxxxxxxxxx"}'
# → { "token": "<JWT>", "expiresIn": 3600, "plan": "free" }
# 2. Call any data endpoint with the token
curl "https://pigi.finance/api/v1/vaults?limit=5" \
-H "Authorization: Bearer <JWT>"Keep your API key server-side. The short-lived token is safe to use from the browser. A token from this API can't be used on any other pigi endpoint and vice-versa.
Plans
Three plans — Free, Pro, Custom — see the comparison table. Every plan gets every rate, TVL and window figure, the holder count, and Basic risk (band, overall score and their history — what the vault page shows to anyone). Pro adds the holder list with its concentration figures, and the holder-analytics fields on the Vault object. Custom adds the full risk decomposition. Your plan is echoed by /auth/token and /usage.
Gating is explicit, so "not on your plan" is never mistaken for "no data":
- A gated field comes back
nulland is named in a top-levelplan_requiredobject on the response, e.g."plan_required": { "deposit_count": "pro" }. A field that is null for another reason (not indexed yet, vault not assessed) carries no marker. - A gated endpoint answers
403 { "error": "plan_required", "required": "pro" }.
| Parameter | Type | Description |
|---|---|---|
| Vault.holders | Free | Holder count. |
| GET /vaults/:id/holders | Pro | Holder list + concentration. 403 plan_required on Free. |
| Vault.deposit_count / active_addresses_30d / tvl_concentration_top10pct | Pro | Holder analytics. null + plan_required on Free. |
| GET /vaults/:id/risk — band, score, history, floors | Free | Basic risk, every plan. |
| GET /vaults/:id/risk — full | Custom | Rosette, anchor, dependency cap, decomposition. null + plan_required below Custom. |
| GET /protocols, /protocols/:node_id — score, band, node rating, vault contributions | Free | Cumulative protocol rating, every plan. |
| GET /protocols/:node_id — rubric | Custom | The node's rubric lines (rung + points). null + plan_required below Custom. |
Risk bands are not credit ratings
A pigi risk band (band, risk_band) and its 0–100 score are a relative assessment of the risk of loss to a depositor's position in a DeFi vault, compared across the vaults pigi tracks. A protocol band applies the same scale to a protocol as a dependency of the vaults that deposit into it.
Bands and scores are not credit ratings. They are not issued in accordance with Regulation (EC) No 1060/2009 on credit rating agencies, and pigi is not a registered or certified credit rating agency in the EU, the UK or anywhere else. A band does not address the ability or willingness of any person, protocol or issuer to meet payment or other financial obligations, and it is not an opinion on the creditworthiness of any entity, debt or financial instrument. Nothing in this API is financial advice.
Fields carry the bare letter, "A" … "F". pigi displays bands with the (rb) suffix, e.g. B(rb), so the scale is not mistaken for a credit-rating scale. The methodology is described on the risk methodology page.
Endpoints
/api/v1/auth/tokenNo authExchange an API key for a short-lived bearer token.
Request body (JSON)
| Parameter | Type | Description |
|---|---|---|
| apiKey | string | Your secret API key. |
Response
{ "token": "<JWT>", "expiresIn": 3600, "plan": "free" }/api/v1/vaultsBearer tokenList vaults with optional filters and pagination.
Query parameters (all optional)
| Parameter | Type | Description |
|---|---|---|
| protocol_name | string | Filter by protocol (e.g. Aave, Morpho, Euler, Uniswap). |
| chain_id | number | Filter by chain id (e.g. 1, 8453, 42161). |
| strategy_id | number | Filter by a strategy (compat) id. |
| tvl_filter | string | TVL band, e.g. gte_1m, gte_5m, non_na, na. |
| apr_filter | string | APR band, e.g. gt_5, gt_10, lte_10, non_na, na. |
| age_filter | string | Pool age, e.g. new, 3mo, 6mo, 12mo, non_na, na. |
| asset_class | string | Denomination class: stable, mixed, non-stable. Comma-separate for several. |
| search | string | Case-insensitive substring match on the vault name, e.g. USDC, WETH, Gauntlet. |
| risk_band | string | Keep only these published bands, e.g. A,B. Unrated vaults never match. |
| min_risk_score | number | Keep vaults whose published risk_score is at least this (0–100). Unrated vaults never match. |
| sort | string | id_asc (default), apr_desc, tvl_desc, risk_score_desc, risk_adjusted_apr_desc. Nulls sort last. Unknown values return 400 invalid_sort. |
| limit | number | Page size. Default 100, max 1000. |
| offset | number | Rows to skip. Default 0. |
Response
{
"data": [ { /* Vault */ } ],
"pagination": { "total": 1342, "limit": 100, "offset": 0, "hasMore": true },
"availableFilters": { "protocol_names": [...], "chain_ids": {...}, "all_chain_ids": [...] },
"plan_required": { "deposit_count": "pro", "active_addresses_30d": "pro",
"tvl_concentration_top10pct": "pro" } // Free plan only
}/api/v1/vaults/:idBearer tokenFetch a single vault by its pool id. Returns 404 if not found.
Response
{ "data": { /* Vault */ }, "plan_required": { /* Free plan only, see Plans */ } }/api/v1/vaults/:id/historyBearer tokenDaily time-series for a vault. Here :id is the strategy id (strategy_id on the Vault object).
Query parameters
| Parameter | Type | Description |
|---|---|---|
| range | string | Window: 7D, 30D, 90D, or 180D. Default 7D. |
Response
{
"data": [
{ "timestamp": "2026-06-23T00:00:00Z", "tvl": 12500000, "apr": 6.21,
"apy": 6.40, "ra_apr": 5.11, "tvl_30d_ma": 12100000, "apr_30d_ma": 6.05,
"apy_30d_ma": 6.18 }
],
"lastTvl": 12500000, "lastApr": 6.21, "lastApy": 6.40, "lastRaApr": 5.11,
"lastTvl30dMa": 12100000, "lastApr30dMa": 6.05, "lastApy30dMa": 6.18, "lastRaApr30dMa": 4.95
}ra_apr is risk-adjusted APR: APR minus a penalty derived from the vault's published overall risk score (0–100, higher = safer); it is null when the vault has no published assessment.
The *_30d_ma fields are simple arithmetic means of the last 30 daily values. For APR that equals the 30-day window rate (APR is linear). For APY it does not: apy_30d_ma is the average of 30 annualized daily rates, not the return you would get by compounding those 30 days together. The two coincide on stable vaults and diverge on volatile ones — averaging convex daily APYs overstates the realized figure, by more the wider the day-to-day swings. For the return a holder actually experienced, use /stats: its window apy and the NAV-based cagr are point-to-point. The same definition applies to the base-rate series below.
/api/v1/vaults/:id/statsBearer tokenAggregated stats over fixed windows for a vault (:id = strategy id). Omit period to get all windows.
Query parameters
| Parameter | Type | Description |
|---|---|---|
| period | string | weekly, monthly, quarterly, yearly, or lifetime (optional). |
Response
{
"weekly": { "tvl_low": 11.8e6, "tvl_high": 12.6e6, "apr": 6.1, "apy": 6.3,
"inflows": 2.1e5, "period_start": "...", "period_end": "...",
"cagr": 6.2, "volatility": 1.4, "sharpe": 1.8, "sortino": 2.3,
"nav_period_start": null, "nav_period_end": null },
"monthly": { ... }, "quarterly": { ... }, "yearly": { ... },
"lifetime": { ... }
}inflows is the net TVL change over the window (last day − first day): positive = net inflow, negative = net outflow. It is TVL-based, so it also reflects yield earned over the window, not just deposits/withdrawals. Each window also includes risk/return metrics — cagr, volatility, sharpe, and sortino — computed from the vault's share-price (NAV) series; they are null for vaults without one (e.g. Uniswap pools) — plus a lifetime window — which covers every day pigi has indexed the vault, so its period_start is our first indexed day rather than the vault's launch. On lifetime, read those four against nav_period_start / nav_period_end rather than the period: apr and apy cover every day with a quoted rate, while the share-price series is often shorter.
/api/v1/vaults/:id/holdersBearer tokenPro plan. The vault's holder list — :id = strategy id — ranked by share-token balance, with each address's share of the total and the cumulative share down the ranking. A Free client gets 403 { "error": "plan_required", "required": "pro" }. (Vault.holders gives the holder count on every plan.)
Query parameters
| Parameter | Type | Description |
|---|---|---|
| limit | number | Page size. Default 100, max 500. |
| offset | number | Rows to skip. Default 0. |
Response
{
"data": [
{ "rank": 1, "address": "0x1a2b…c3d4", "balance": 1.2345e+22,
"share_pct": 31.2, "cumulative_pct": 31.2 }
],
"meta": {
"strategy_id": 4321, "holders": 812, "total_balance": 3.9e+22, "total_known": true,
"page": { "limit": 100, "offset": 0, "total_rows": 812 },
"scan": { "status": "completed", "last_committed_block": 23456789,
"full_scan_at": "2026-09-01T02:10:00Z", "updated_at": "2026-09-06T02:12:41Z" },
"concentration": { "block_number": 23456789, "holders": 812, "total_balance": 3.9e+22,
"decimals": 18, "top1_share": 0.312, "top5_share": 0.654,
"top10_share": 0.781, "hhi": 0.1421, "gini": 0.83, "holders_gt_1pct": 14,
"deposit_events": 2310, "active_addresses_30d": 57, "window_start_block": 23240789 }
}
}The list is replayed from on-chain transfers by pigi's holders scan. A vault that has not been scanned with per-address storage yet returns data: [] with meta.scan: null — not scanned, not zero holders. Every scan is checked against the vault's own totalSupply(); one that does not add up returns meta.scan.status: "inconsistent" with no rows and null meta.holders / meta.concentration until a later scan passes. The vault's own address is never listed. balance is in raw share-token units and loses precision above 2^53; read the shares, not the raw figure. Above 20,000 holders the total is not walked and share_pct is null with total_known: false. meta.concentration is the map's concentration at the scan's block — top-1 / top-5 / top-10 share (fractions of all positive balances), HHI (sum of squared shares, 1 = a single holder), Gini (0 = all holders equal) and the count of addresses above 1% — or null until the vault's next scan writes it.
/api/v1/vaults/:id/riskBearer tokenpigi's published risk assessment for a vault — :id = strategy id. Every plan gets the Basic block: the band and overall score (0–100, higher = safer), their date-keyed history, the band thresholds under the current methodology, the event markers, and any binding hard floor (the standing warning, e.g. "redemption blocked"). Custom additionally gets full: the structural rosette with its ladder rungs and precedents, the quantitative anchor inputs, the dependency cap (edges, min, slack, whether it binds), overlays, floors with their cap arithmetic, the blend and the final computation, plus the per-day anchor / rosette / cap series. Below Custom, full is null with "plan_required": { "full": "custom" }. The band is a relative risk-of-loss assessment, not a credit rating — see Risk bands.
Query parameters
| Parameter | Type | Description |
|---|---|---|
| range | string | History window: 7D, 30D, 90D, 180D, 365D or ALL. Default 90D. |
Response
{
"strategy_id": 25, "assessed": true,
"band": "B", "score": 74.2, "date": "2026-09-06", "methodology_version": "1.1.0",
"bands": [["A", 85], ["B", 70], ["C", 55], ["D", 40], ["F", 0]],
"floors": [],
"range": "90D", "from": "2026-06-09", "to": "2026-09-06",
"history": [ { "date": "2026-09-06", "band": "B", "score": 74.2,
"methodology_version": "1.1.0", "event_flags": [] } ],
"events": [ { "date": "2026-08-14", "event_type": "hard_floor",
"title": "Redemptions paused", "is_global": false } ],
"full": null, // Custom: the decomposition (see below)
"plan_required": { "full": "custom" } // absent on Custom
}
// full (Custom plan):
{ "methodology_version": "1.1.0", "raw_band": "B",
"blend": { "rosette_weight": 0.6, "anchor_weight": 0.4 },
"rosette": { "score": 68, "dimensions": [ { "key": "contract", "label": "Contract & platform security",
"weight": 0.22, "score": 72, "points": 18, "max": 25,
"subscores": [ { "key": "audit_coverage", "label": "Audit coverage & recency",
"position": "audited, <12mo", "points": 7, "max": 9,
"applicable": true, "precedent": "…" } ] } ] },
"anchor": { "score": 77, "points": 77, "max": 100,
"inputs": [ { "key": "tvl", "label": "TVL", "value": 306060998, "unit": "usd",
"band": "$100–500M", "points": 20, "max": 25 } ] },
"dependency_cap": { "cap": 82, "binding": false, "critical_dep_min": 72, "slack": 10,
"critical_edges": ["DEPOSITS_INTO", "PRICED_BY"],
"edges": [ { "type": "PRICED_BY", "node_id": "chainlink-usdc-usd",
"label": "Chainlink USDC/USD", "node_type": "oracle",
"critical": true, "rated": true, "rating": 72,
"unrated_reason": null } ] },
"overlays": { "clamp": 8, "net": 0, "applied": [] },
"floors": [],
"computation": { "s_raw": 71.6, "overlays_net": 0, "cap_applied": null, "final": 74.2 },
"history": [ { "date": "2026-09-06", "anchor_score": 77, "rosette_score": 68, "dependency_cap": 82 } ] }assessed: false (with null / empty fields) means the vault has no published assessment — most vaults. 503 risk_unavailable means the risk database could not be reached; retry.
/api/v1/protocolsBearer tokenpigi's cumulative protocol rating for every rated protocol node, in one call. pigi rates vaults, and rates protocols as the dependency nodes that cap the vaults depositing into them; this endpoint blends the two into one published figure:
score = 0.6 × the node's own rating
+ 0.4 × TVL-weighted mean of the latest published score of every rated vault that deposits into itThe weights are pigi's published formula (echoed in formula) and are not adjustable by the caller, so the number can be carried verbatim as pigi's own. With no rated vault the node rating stands alone (applied.k1 = 1). The band is read straight off the methodology thresholds, without the dwell rule that smooths vault bands. date is the last date the number could have changed — the newer of the node's rating date and the newest vault row. Like vault bands, protocol bands are not credit ratings — see Risk bands.
Query parameters
| Parameter | Type | Description |
|---|---|---|
| chain_id | integer | Count only the depositing vaults on this chain (1 = Ethereum). Narrows the vault set only — the node term is unchanged. |
Response
{
"data": [
{ "node_id": "aave-v3-ethereum", "label": "Aave V3 — Ethereum",
"score": 66.9, "band": "C", "date": "2026-10-06",
"applied": { "k1": 0.6, "k2": 0.4, "vault_aggregation": "tvl_weighted_mean" },
"protocol": { "rating": 62, "date": "2026-09-14" },
"vaults": { "rated_count": 24, "tvl_usd": 18234567890.12,
"score_tvl_weighted": 74.25, "score_mean": 72.8 } },
{ "node_id": "lido-protocol", "label": "Lido",
"score": 81, "band": "B", "date": "2026-08-20",
"applied": { "k1": 1, "k2": 0, "vault_aggregation": "none" },
"protocol": { "rating": 81, "date": "2026-08-20" },
"vaults": { "rated_count": 0, "tvl_usd": 0, "score_tvl_weighted": null, "score_mean": null } }
],
"count": 34, "chain_id": null,
"formula": { "k1": 0.6, "k2": 0.4, "vault_aggregation": "tvl_weighted_mean", "vault_max_age_days": 7,
"bands": [["A", 85], ["B", 70], ["C", 55], ["D", 40], ["F", 0]],
"note": "score = 0.6 × the protocol node's own rating + 0.4 × …" }
}A vault counts when its latest published row deposits into the node, is at most vault_max_age_days old and the vault is still listed. A Morpho vault deposits into two protocol nodes (the framework and the Blue instance) and counts toward each. Framework-wide nodes (euler-v2, uniswap-v3, metamorpho-v1-1) mix chains unless you pass chain_id.
/api/v1/protocols/:node_idBearer tokenThe same figure for one protocol node — :node_id as listed by /protocols (e.g. aave-v3-ethereum, euler-v2, morpho-blue-ethereum) — plus vaults.items: every rated vault that counted, with its published score and band, TVL and the share of the vault term it carried. Custom additionally gets rubric: the node's own rubric lines (audit & bounty, admin authority, pause authority, operational history, scale) with the rung and points on each. Below Custom, rubric is null with "plan_required": { "rubric": "custom" }.
Query parameters
| Parameter | Type | Description |
|---|---|---|
| chain_id | integer | Count only the depositing vaults on this chain. Narrows the vault set only. |
Response
{
"node_id": "aave-v3-ethereum", "label": "Aave V3 — Ethereum", "assessed": true,
"score": 66.9, "band": "C", "date": "2026-10-06", "chain_id": null,
"formula": { "k1": 0.6, "k2": 0.4, "vault_aggregation": "tvl_weighted_mean", "vault_max_age_days": 7,
"bands": [["A", 85], ["B", 70], ["C", 55], ["D", 40], ["F", 0]], "note": "…" },
"applied": { "k1": 0.6, "k2": 0.4, "vault_aggregation": "tvl_weighted_mean" },
"protocol": { "rating": 62, "date": "2026-09-14", "methodology_version": "1.15.0", "source": "in-house" },
"vaults": {
"rated_count": 24, "stale_excluded": 0, "tvl_usd": 18234567890.12,
"score_tvl_weighted": 74.25, "score_mean": 72.8, "score_min": 58.4, "score_max": 77,
"latest_date": "2026-10-06", "bands": { "A": 0, "B": 19, "C": 5, "D": 0, "F": 0 }, "chain_ids": [1],
"items": [ { "strategy_id": 10006, "name": "Aave V3 USDC", "chain_id": 1, "protocol_name": "Aave",
"score": 77, "band": "B", "date": "2026-10-06", "tvl_usd": 4123456789.5,
"weight": 0.2262, "critical": true, "cap_binding": true } ]
},
"rubric": null, // Custom: the node's rubric lines (see below)
"plan_required": { "rubric": "custom" } // absent on Custom
}
// rubric (Custom plan):
{ "methodology_version": "1.15.0", "subtype": null, "points": 62, "max": 100,
"lines": [ { "key": "audit_and_bounty", "label": "Audit coverage & live bounty",
"position": "multi_audit_scaled_bounty_ci", "points": 30, "max": 30 },
{ "key": "operational_history", "label": "Operational history",
"position": "unremediated_loss", "points": 0, "max": 25 } ] }404 not_found means no rated protocol node has that id (curator and oracle nodes are not served here). cap_binding marks the vaults whose own score is capped by this node's rating; stale_excluded counts depositing vaults dropped because their latest published row is past the freshness window.
/api/v1/ratesBearer tokenDaily history of the DeFi Base Rate — the mean yield across the tracked stablecoin (stable) and ETH (eth) vault sets — plus the 3-month U.S. T-Bill risk-free rate (tbills). No parameters; each series is sorted oldest to newest.
Response
{
"stable": [ { "timestamp": "2026-06-23T00:00:00Z", "apr": 5.4, "apy": 5.55,
"apr_30d_ma": 5.2, "apy_30d_ma": 5.34 } ],
"eth": [ { "timestamp": "2026-06-23T00:00:00Z", "apr": 3.1, "apy": 3.15,
"apr_30d_ma": 3.0, "apy_30d_ma": 3.05 } ],
"tbills": [ { "timestamp": "2026-06-23T00:00:00Z", "rate": 3.79 } ]
}/api/v1/hacksBearer tokenHack and loss events — every tracked DeFi exploit plus a reference set of major TradFi banking losses, the dataset behind our DeFi vs TradFi losses analysis. Each event carries its date, the protocol or institution, the amount lost in USD, and — for DeFi — what kind of protocol was hit.
Query parameters (all optional)
| Parameter | Type | Description |
|---|---|---|
| category | string | defi, tradfi, or all. Default all. |
| type | string | DeFi, Dexes, or Bridges (case-insensitive). DeFi events only — TradFi rows carry no type, so this excludes them. |
| from | string | Only events on/after this date — YYYY-MM-DD or ISO 8601. |
| to | string | Only events on/before this date (inclusive of the whole day). |
| min_amount | number | Only events at or above this USD amount. |
| sort | string | date_asc (default), date_desc, amount_desc, or amount_asc. |
| limit | number | Page size. Default 100, max 1000. |
| offset | number | Rows to skip. Default 0. |
Response
{
"data": [
{ "id": 1937, "date": "2022-03-28T23:00:00+00:00", "name": "Ronin",
"amount_hacked": 624000000, "category": "defi", "type": "Bridges" }
],
"summary": { "count": 285, "total_amount_hacked": 18824475500,
"first_date": "2020-09-28T23:00:00+00:00",
"last_date": "2026-07-10T00:00:00+00:00" },
"pagination": { "total": 285, "limit": 100, "offset": 0, "hasMore": true }
}summary covers the entire filtered set, not just the returned page — so total losses for a window can be read without paging through every event.
type is present on DeFi events only, and is one of DeFi (lending, yield, staking, stablecoins), Dexes, or Bridges. TradFi events are bank failures, which the taxonomy does not describe, so they omit the field.
Example — DeFi losses in 2025, biggest first
curl -H "Authorization: Bearer $TOKEN" \ "https://pigi.finance/api/v1/hacks?category=defi&from=2025-01-01&to=2025-12-31&sort=amount_desc"
Example — total ever lost to bridge exploits
curl -H "Authorization: Bearer $TOKEN" \ "https://pigi.finance/api/v1/hacks?type=bridges&limit=1" # read summary.total_amount_hacked
/api/v1/usageBearer tokenYour request count for the current calendar month (UTC) and your plan limit.
Response
{
"clientId": 1, "plan": "free",
"period": { "start": "2026-06-01T00:00:00Z", "end": "2026-07-01T00:00:00Z" },
"used": 1423, "limit": 100000, "remaining": 98577
}limit and remaining are null when your plan has no cap.
/api/v1No authDiscovery index — lists the available endpoints and the docs URL.
The Vault object
Returned by /vaults and /vaults/:id.
| Parameter | Type | Description |
|---|---|---|
| id | number | Pool id (use for /vaults/:id). |
| strategy_id | number | Strategy id (use for /history and /stats). |
| protocol_name | string | Protocol, e.g. Aave, Morpho, Euler. |
| chain_id | number | Chain id. |
| pool_name | string | The catalogue / on-chain vault name. Not unique — e.g. "EVK Vault eUSDC-2" exists on five chains. |
| display_name | string | null | pool_name plus the minimum qualifier (chain · symbol · framework) that makes it unique. Prefer this for display; null for a vault added since the last nightly. |
| pool_address | string | On-chain pool/vault address. |
| asset_address | string | null | Underlying asset address. |
| asset_class | string | null | Denomination class of the vault's assets: stable, mixed, or non-stable. |
| type | string | Pool-type category (Lending, AMM, …). |
| tvl_30d_ma | number | 30-day moving-average TVL (USD). |
| apr_30d_ma | number | 30-day moving-average APR (%). |
| holders | number | null | Holder count (every plan). null while the vault's last holders scan failed verification. |
| pool_creation_date | string | null | ISO date the pool was created. |
| updated_at | string | null | Block-time of the newest data point. |
| tvl_flow_1d | number | null | 1-day TVL flow. |
| tvl_flow_7d | number | null | 7-day TVL flow. |
| apr_trend_1d | number | null | 1-day APR trend. |
| risk_band | string | null | Published pigi risk band: A (safest) … F, displayed by pigi as A(rb) … F(rb). A relative risk-of-loss assessment, not a credit rating (see Risk bands). null = not assessed yet. |
| risk_score | number | null | Published overall risk score, 0–100 (higher = safer). null = not assessed yet. |
| risk_adjusted_apr | number | null | apr_30d_ma minus the risk penalty derived from the published score (the same adjustment as ra_apr in /history). null when unrated or without APR. |
| active_addresses_30d | number | null | Pro. Distinct depositors in the trailing 30 days ending at the last scan: owners on ERC-4626 Deposit logs; for Uniswap V3 the current owners of positions minted in the window. null + plan_required on Free; null without a marker until the vault has been scanned (Aave reserves are not scanned). |
| deposit_count | number | null | Pro. Deposit events over the vault's indexed life: ERC-4626 Deposit logs (Morpho, Euler, Yearn), pool Mint logs for Uniswap V3; withdrawals not counted. null + plan_required on Free; null without a marker until the vault has been scanned (Aave reserves are not scanned). |
| tvl_concentration_top10pct | number | null | Pro. % of TVL held by the top 10% of holders. null + plan_required on Free; null without a marker while holder analytics are still being indexed. |
Errors & conventions
Errors are JSON: { "error": "<code>" }. Successful data responses are cached for 5 minutes (Cache-Control: public, max-age=300); auth and usage responses are never cached. Every authenticated request counts toward your monthly usage. Each response carries RateLimit-Limit/RateLimit-Remaining/RateLimit-Reset; once you reach your plan's monthly limit, requests return 429 (with Retry-After) until it resets at the start of the next UTC month.
| Parameter | Type | Description |
|---|---|---|
| 400 | missing_api_key / invalid_id | Malformed request. |
| 401 | invalid_api_key / invalid_token | Bad key on exchange, or missing/expired token. |
| 403 | client_suspended | Your API client is suspended. |
| 403 | plan_required | The endpoint needs a higher plan — `required` names it (see Plans). |
| 404 | not_found | No vault with that id, or no rated protocol node with that node_id. |
| 405 | method_not_allowed | Wrong HTTP method for the route. |
| 429 | rate_limit_exceeded | Monthly request limit reached — see RateLimit-* / Retry-After; resets at the start of next month (UTC). |
| 500 | internal_error | Unexpected server error. |
| 503 | risk_unavailable | /vaults/:id/risk and /protocols*: the risk database could not be reached. Retry. |