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.

    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.
    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": [...] }
    }
    GET/api/v1/vaults/:idBearer token

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

    Response

    { "data": { /* Vault */ } }
    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.

    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 },
      "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 — plus a lifetime (all-time) window.

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

    The vault's holder list — :id = strategy id. Each entry is an address with its balance and share of the vault's TVL. (Vault.holders gives just the holder count.)

    Response

    {
      "data": [
        { "address": "0x1a2b…c3d4", "balance": 12345.6, "share_pct": 4.2 }
      ]
    }
    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_namestringHuman-readable vault/pool name.
    pool_addressstringOn-chain pool/vault address.
    asset_addressstring | nullUnderlying asset address.
    typestringPool-type category (Lending, AMM, …).
    tvl_30d_manumber30-day moving-average TVL (USD).
    apr_30d_manumber30-day moving-average APR (%).
    holdersnumber | nullHolder count.
    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. null = not assessed yet.
    active_addressesnumber | nullCount of active holder addresses.
    deposit_countnumber | nullNumber of deposit events.
    tvl_concentration_top10pctnumber | null% of TVL held by the top 10% of holders.

    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.
    404not_foundNo vault with that 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.