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_….

    Base URL
    https://pigi.finance/api/v1

    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 null and is named in a top-level plan_required object 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" }.
    ParameterTypeDescription
    Vault.holdersFreeHolder count.
    GET /vaults/:id/holdersProHolder list + concentration. 403 plan_required on Free.
    Vault.deposit_count / active_addresses_30d / tvl_concentration_top10pctProHolder analytics. null + plan_required on Free.
    GET /vaults/:id/risk — band, score, history, floorsFreeBasic risk, every plan.
    GET /vaults/:id/risk — fullCustomRosette, anchor, dependency cap, decomposition. null + plan_required below Custom.
    GET /protocols, /protocols/:node_id — score, band, node rating, vault contributionsFreeCumulative protocol rating, every plan.
    GET /protocols/:node_id — rubricCustomThe 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

    POST/api/v1/auth/tokenNo auth

    Exchange an API key for a short-lived bearer token.

    Request body (JSON)

    ParameterTypeDescription
    apiKeystringYour secret API key.

    Response

    { "token": "<JWT>", "expiresIn": 3600, "plan": "free" }
    GET/api/v1/vaultsBearer token

    List vaults with optional filters and pagination.

    Query parameters (all optional)

    ParameterTypeDescription
    protocol_namestringFilter by protocol (e.g. Aave, Morpho, Euler, Uniswap).
    chain_idnumberFilter by chain id (e.g. 1, 8453, 42161).
    strategy_idnumberFilter by a strategy (compat) id.
    tvl_filterstringTVL band, e.g. gte_1m, gte_5m, non_na, na.
    apr_filterstringAPR band, e.g. gt_5, gt_10, lte_10, non_na, na.
    age_filterstringPool age, e.g. new, 3mo, 6mo, 12mo, non_na, na.
    asset_classstringDenomination class: stable, mixed, non-stable. Comma-separate for several.
    searchstringCase-insensitive substring match on the vault name, e.g. USDC, WETH, Gauntlet.
    risk_bandstringKeep only these published bands, e.g. A,B. Unrated vaults never match.
    min_risk_scorenumberKeep vaults whose published risk_score is at least this (0–100). Unrated vaults never match.
    sortstringid_asc (default), apr_desc, tvl_desc, risk_score_desc, risk_adjusted_apr_desc. Nulls sort last. Unknown values return 400 invalid_sort.
    limitnumberPage size. Default 100, max 1000.
    offsetnumberRows 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
    }
    GET/api/v1/vaults/:idBearer token

    Fetch a single vault by its pool id. Returns 404 if not found.

    Response

    { "data": { /* Vault */ }, "plan_required": { /* Free plan only, see Plans */ } }
    GET/api/v1/vaults/:id/historyBearer token

    Daily time-series for a vault. Here :id is the strategy id (strategy_id on the Vault object).

    Query parameters

    ParameterTypeDescription
    rangestringWindow: 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.

    GET/api/v1/vaults/:id/statsBearer token

    Aggregated stats over fixed windows for a vault (:id = strategy id). Omit period to get all windows.

    Query parameters

    ParameterTypeDescription
    periodstringweekly, 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.

    GET/api/v1/vaults/:id/holdersBearer token

    Pro 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

    ParameterTypeDescription
    limitnumberPage size. Default 100, max 500.
    offsetnumberRows 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.

    GET/api/v1/vaults/:id/riskBearer token

    pigi'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

    ParameterTypeDescription
    rangestringHistory 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.

    GET/api/v1/protocolsBearer token

    pigi'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 it

    The 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

    ParameterTypeDescription
    chain_idintegerCount 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.

    GET/api/v1/protocols/:node_idBearer token

    The 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

    ParameterTypeDescription
    chain_idintegerCount 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.

    GET/api/v1/ratesBearer token

    Daily 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 } ]
    }
    GET/api/v1/hacksBearer token

    Hack 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)

    ParameterTypeDescription
    categorystringdefi, tradfi, or all. Default all.
    typestringDeFi, Dexes, or Bridges (case-insensitive). DeFi events only — TradFi rows carry no type, so this excludes them.
    fromstringOnly events on/after this date — YYYY-MM-DD or ISO 8601.
    tostringOnly events on/before this date (inclusive of the whole day).
    min_amountnumberOnly events at or above this USD amount.
    sortstringdate_asc (default), date_desc, amount_desc, or amount_asc.
    limitnumberPage size. Default 100, max 1000.
    offsetnumberRows 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
    GET/api/v1/usageBearer token

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

    GET/api/v1No auth

    Discovery index — lists the available endpoints and the docs URL.

    The Vault object

    Returned by /vaults and /vaults/:id.

    ParameterTypeDescription
    idnumberPool id (use for /vaults/:id).
    strategy_idnumberStrategy id (use for /history and /stats).
    protocol_namestringProtocol, e.g. Aave, Morpho, Euler.
    chain_idnumberChain id.
    pool_namestringThe catalogue / on-chain vault name. Not unique — e.g. "EVK Vault eUSDC-2" exists on five chains.
    display_namestring | nullpool_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_addressstringOn-chain pool/vault address.
    asset_addressstring | nullUnderlying asset address.
    asset_classstring | nullDenomination class of the vault's assets: stable, mixed, or non-stable.
    typestringPool-type category (Lending, AMM, …).
    tvl_30d_manumber30-day moving-average TVL (USD).
    apr_30d_manumber30-day moving-average APR (%).
    holdersnumber | nullHolder count (every plan). null while the vault's last holders scan failed verification.
    pool_creation_datestring | nullISO date the pool was created.
    updated_atstring | nullBlock-time of the newest data point.
    tvl_flow_1dnumber | null1-day TVL flow.
    tvl_flow_7dnumber | null7-day TVL flow.
    apr_trend_1dnumber | null1-day APR trend.
    risk_bandstring | nullPublished 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_scorenumber | nullPublished overall risk score, 0–100 (higher = safer). null = not assessed yet.
    risk_adjusted_aprnumber | nullapr_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_30dnumber | nullPro. 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_countnumber | nullPro. 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_top10pctnumber | nullPro. % 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.

    ParameterTypeDescription
    400missing_api_key / invalid_idMalformed request.
    401invalid_api_key / invalid_tokenBad key on exchange, or missing/expired token.
    403client_suspendedYour API client is suspended.
    403plan_requiredThe endpoint needs a higher plan — `required` names it (see Plans).
    404not_foundNo vault with that id, or no rated protocol node with that node_id.
    405method_not_allowedWrong HTTP method for the route.
    429rate_limit_exceededMonthly request limit reached — see RateLimit-* / Retry-After; resets at the start of next month (UTC).
    500internal_errorUnexpected server error.
    503risk_unavailable/vaults/:id/risk and /protocols*: the risk database could not be reached. Retry.