Vaults

GET /v1/vaults — search and filter 1000+ vaults by chain, protocol, tier, APR, TVL, and available liquidity.

GET /v1/vaults

List all vaults with filtering, sorting, and pagination.

Query Parameters

ParameterTypeDefaultDescription
pagenumber1Page number
limitnumber20Results per page, max 100
chainstring—Filter by chain (comma-separated: ethereum,base)
protocolstring—Filter by protocol ID (comma-separated: morpho,aave)
curatorstring—Filter by curator ID (comma-separated)
assetstring—Filter by asset symbol (comma-separated: USDC,WETH)
riskTierstring—Filter by risk tier (comma-separated, case-insensitive: prime, core, edge)
minRiskScorenumber—Minimum risk score (0–10, inclusive). Vaults without a score are excluded.
maxRiskScorenumber—Maximum risk score (0–10, inclusive). Vaults without a score are excluded.
minScorenumber—Alias of minRiskScore (vaults.fyi-style, 0–10). See below.
min_scorenumber—snake_case alias of minScore.
searchstring—Search vault name, symbol, asset, protocol, curator
minTvlstring—Minimum TVL in USD
stablecoinstring—Filter stablecoin vaults (true/false)
lsdstring—Filter LSD vaults (true/false)
singleExposurestring—Filter single-exposure vaults (true/false)
noILstring—Filter no impermanent loss (true/false)
auditedstring—Filter audited vaults (true/false)
highConfidencestring—Filter high-confidence vaults (true/false)
depositablestring—Filter by current deposit capacity (true/false). See below.
sortBystring—Sort field: tvl_usd, available_liquidity_usd, liquidity_deficit_usd, apr_net, borrow_apr_net, borrow_reward_apr, utilization, real_yield_share, name, total_score, last_synced_at
sortOrderstringdescSort order: asc or desc

The depositable filter

Sourced from the ERC-4626 maxDeposit(address(0)) call against each vault. Captures everything that gates new deposits at the contract level: caps reached, vault paused, supply queue saturated, gates blocking new shares.

Each vault response carries deposit_status ("open", "closed", or "unknown"). The filter maps to those states:

  • depositable=true → returns open and unknown vaults — the "not explicitly closed" set. Use this when you don't want to drop vaults that haven't been on-chain probed yet (typically non-ERC4626 protocols).
  • depositable=false → returns only closed vaults (confirmed maxDeposit == 0 on chain). Useful for monitoring exit-only positions.
  • Omit the parameter for the default unfiltered list.

For strict server-side filtering (only confirmed-open, exclude unknowns), filter on the response field client-side: data.filter(v => v.deposit_status === 'open'). A ?deposit_status=open query is on the roadmap.

The minScore filter (vaults.fyi mirror)

Integrators familiar with vaults.fyi minVaultScore (0–100, Reputation Score) often want the same pattern on Philidor's 0–10 risk score.

vaults.fyiPhilidor equivalent
minVaultScore=80minScore=8.0 or riskTier=prime
minVaultScore=70minScore=7.0 or riskTier=prime,core
minVaultScore=50minScore=5.0 or riskTier=prime,core,edge

Filter server-side with minScore (or min_score), the 0–10 mirror of vaults.fyi minVaultScore / 10:

const res = await fetch(
  'https://api.philidor.io/v1/vaults?minScore=7.0&depositable=true&asset=USDC&limit=100'
);
const { data } = await res.json();

minScore and min_score are aliases of the canonical minRiskScore — all three accept a 0–10 score and exclude vaults that carry no score. Combine with riskTier for tier-band filtering.

Note: riskTier values are case-insensitive — prime and Prime both resolve to the same tier.

Liquidity availability and deficits

Lending-vault results separate immediately available capital from over-utilization:

available_liquidity_usd = max(0, tvl_usd × (1 − clamp(utilization, 0, 1)))
liquidity_deficit_usd   = max(0, tvl_usd × (utilization − 1))

liquidity_status makes the interpretation explicit:

StatusMeaning
availableAvailable liquidity is positive and the deficit is zero.
exhaustedAvailable liquidity and deficit are both zero.
deficitRaw utilization exceeds 100%; available liquidity is zero and the deficit is positive when TVL is positive.
unknownTVL or utilization is unknown or inapplicable; both dollar fields are null.

The raw utilization field is not clamped and can exceed 1. A deficit status is a distress signal, but does not by itself prove realized bad debt.

This is a point-in-time catalog metric, not a guaranteed executable withdrawal quote. Protocol queues, pauses, gates, market movements, and transaction ordering may constrain the amount a user can withdraw. Use freshness_seconds and withdraw_status alongside it for routing decisions.

Borrow availability

borrow_status is the reserve-level signal for whether new borrowing is currently possible. It is deliberately separate from withdraw-side liquidity_status, the numeric borrow_apr, and the observation-quality field borrow_apr_status. In particular, borrow_apr: 0 can be an open idle market or a disabled reserve; consumers must read the status rather than infer it from the rate.

StatusMeaning
openSource-backed protocol state permits new borrowing.
disabledBorrowing is not enabled for this reserve.
pausedA protocol pause or reserve freeze blocks borrowing.
cappedAn observed hard borrow cap has no remaining capacity.
unknownPhilidor cannot currently prove the borrow permission.
not_applicableThe product has no reserve-level borrow market.

If a protocol still publishes a rate while paused or capped, the API may retain that numeric quote for context; borrow_status takes precedence for availability.

Borrow incentive decomposition

Money-market rows keep the canonical gross borrow quote in borrow_apr and report borrower incentives separately:

borrow_apr_net = borrow_apr - borrow_reward_apr

All three fields are decimal fractions. A negative borrow_apr_net is valid when priced rebates exceed the gross rate. Points programs remain visible in the detail response but contribute zero until they have an observable market price. Position-wide “net APR” claims such as Aave Merit net-position campaigns are retained as evidence but excluded because they cannot be attributed to the borrow leg alone.

borrow_incentive_status prevents missing provider data from silently becoming a zero rebate:

StatusMeaning
observedOne or more active borrower incentive campaigns were observed.
noneThe incentive source was checked successfully and no campaign was active.
unavailablePhilidor could not establish the current incentive state; net APR is null.
not_applicableThe product has no borrower-side market.

has_borrow_rewards is true for priced incentives and unpriced points. Full borrow_rewards rows are detail-only; list responses expose the aggregate fields so bulk scans remain compact.

gross_interest_apy is the lending market's gross supply-side interest before the protocol reserve factor or take, derived as borrow_apr × utilization and returned as a decimal fraction. It is distinct from base_apr, which is the net-of-fee interest contribution. Treat it as an operational pre-fee proxy, not an exact inverse of base_apr, because protocol sources can publish borrow and supply rates on different APR/APY bases. It is independent from borrow_status: existing debt can keep the value nonzero when new borrowing is paused, capped, or disabled, and a real observed zero stays zero. The field is null outside covered money markets or when its inputs are unavailable.

Example

curl "https://api.philidor.io/v1/vaults?riskTier=Prime&asset=USDC&sortBy=apr_net&sortOrder=desc&limit=5"

Response

{
  "data": [
    {
      "id": "morpho-1-0x...",
      "name": "Gauntlet USDC Core",
      "address": "0x...",
      "protocol_id": "morpho",
      "chain_id": 1,
      "chain_name": "Ethereum",
      "asset_symbol": "USDC",
      "tvl_usd": 125000000,
      "apr_net": 0.0523,
      "base_apr": 0.041,
      "utilization": 0.72,
      "borrow_apr": 0.046,
      "borrow_reward_apr": 0.012,
      "borrow_apr_net": 0.034,
      "borrow_incentive_status": "observed",
      "has_borrow_rewards": true,
      "borrow_status": "open",
      "gross_interest_apy": 0.03312,
      "active_loans_usd": 90000000,
      "available_liquidity_usd": 35000000,
      "liquidity_deficit_usd": 0,
      "liquidity_status": "available",
      "total_score": 8.5,
      "risk_tier": "Prime",
      "strategy_type": "isolated_lending",
      "curator_name": "Gauntlet",
      "is_audited": true,
      "deposit_status": "open",
      "is_depositable": true,
      "deposit_capacity_usd": 4250000,
      "last_synced_at": "2026-02-25T12:00:00Z"
    }
  ],
  "meta": {
    "page": 1,
    "limit": 5,
    "total": 42,
    "totalPages": 9
  }
}

GET /v1/vaults/{id}

Get a vault by its database ID.

Path Parameters

ParameterTypeDescription
idstringVault ID (e.g., morpho-1-0x...)

Example

curl https://api.philidor.io/v1/vaults/morpho-1-0x8eb67a509616cd6a7c1b3c8c21d48ff57df3d458

Returns full vault detail with risk vectors, snapshots, events, and rewards. Same schema as vault detail.

Query Parameters

ParameterTypeDefaultDescription
pointsnumber360Snapshot points embedded in the detail response, max 2160
resolutionnumber360Compatibility alias for points, max 2160

POST /v1/vaults/batch

Look up vaults by database ID or contract address. The combined ids and addresses arrays may contain up to 100 items.

Body

{
  "ids": ["morpho-1-0x8eb67a509616cd6a7c1b3c8c21d48ff57df3d458"],
  "addresses": ["0x8eB67A509616cd6A7c1B3c8C21D48FF57df3d458"]
}

Response

{
  "data": [
    {
      "id": "morpho-1-0x8eb67a509616cd6a7c1b3c8c21d48ff57df3d458",
      "name": "Gauntlet USDC Core",
      "total_score": 8.5,
      "risk_tier": "Prime"
    }
  ]
}

GET /v1/vaults/scores

Lightweight bulk screening endpoint. It returns essential score, TVL, APR, and identity fields without full risk vectors.

Query Parameters

The endpoint supports the same filters as GET /v1/vaults. The default limit is 500 and the max is 1000.

Response

{
  "data": [
    {
      "id": "morpho-1-0x8eb67a509616cd6a7c1b3c8c21d48ff57df3d458",
      "address": "0x8eB67A509616cd6A7c1B3c8C21D48FF57df3d458",
      "chain_id": 1,
      "chain_name": "Ethereum",
      "protocol_id": "morpho",
      "asset_symbol": "USDC",
      "tvl_usd": 125000000,
      "apr_net": 0.0523,
      "total_score": 8.5,
      "risk_tier": "Prime",
      "is_active": true,
      "last_synced_at": "2026-07-01T12:00:00Z",
      "score_run_id": "3b1d…",
      "score_computed_at": "2026-07-01T11:58:00.000Z",
      "score_updated_at": "2026-06-28T09:12:00.000Z",
      "freshness_seconds": 120,
      "needs_risk_review": false,
      "is_high_confidence": true,
      "tvl_status": "observed"
    }
  ],
  "meta": {
    "page": 1,
    "limit": 500,
    "total": 1,
    "totalPages": 1
  }
}

Trust signals on every bulk row:

  • freshness_seconds: seconds since the row was last synced, at serve time.
  • score_computed_at: when the scoring run that produced the row completed; score_updated_at: when the published score or tier last changed value. A run that re-confirms the same score moves the first, not the second; moves smaller than 0.05 are not persisted (the deadband).
  • needs_risk_review: true while a data-quality quarantine is active (a reward-APR reconciliation mismatch with the provider, or a non-finite supply APY observation). The score is still served; treat it as provisional until the flag clears.
  • is_high_confidence: a curated shortlist rule, not a confidence interval — total_score at least 7.5 and net APR between 3% and 10%. A Prime vault with a lower or higher yield is false.
  • tvl_status: observed when tvl_usd is a read value; unavailable when the TVL read failed and tvl_usd is served as 0 for compatibility.

GET /v1/vaults/changes

Efficient polling endpoint for vault rows modified after a timestamp or returned cursor.

Requires a Data-plan key. See API Access and Plans.

Query Parameters

ParameterTypeDefaultDescription
sincestringrequiredISO timestamp or cursor from the previous response
limitnumber100Results per request, max 500
changedstringsyncWhich change clock to follow: sync — every sync write (last_synced_at); score — only a change of the published score or tier (score_updated_at). Cursors are built on the chosen clock; do not mix them between modes

Response

{
  "data": {
    "vaults": [
      {
        "id": "morpho-1-0x8eb67a509616cd6a7c1b3c8c21d48ff57df3d458",
        "total_score": 8.5,
        "risk_tier": "Prime",
        "last_synced_at": "2026-07-01T12:00:00Z"
      }
    ],
    "cursor": "2026-07-01T12:00:00.000000Z|morpho-1-0x..."
  },
  "meta": {
    "total": 1,
    "limit": 100,
    "has_more": false
  }
}

The cursor encodes the last row's instant on the chosen clock at microsecond precision and its id; pass it back verbatim as since. meta.total counts rows from the cursor position onward, meta.has_more says whether another page follows. Delivery is at-least-once: a row can appear again when it is re-synced between polls, so deduplicate on (id, last_synced_at) in the default mode and on (id, score_updated_at) with changed=score. Vault rows keep last_synced_at at millisecond precision.

changed=score is the feed an allocator polls for rescores: a sync that leaves the score untouched does not appear, an incident-triggered rescore does. Rows carry the same trust signals as /v1/vaults/scores.


GET /v1/vaults/with-critical-incidents

List vaults that have had critical incidents in the last 365 days (severity Critical or incident severity major), sorted by TVL. An incident linked to a vault directly always counts; a protocol-scoped incident reaches the protocol's vaults only when it is flagged as affecting the protocol score, which keeps depeg monitoring noise from listing every vault of a protocol. Only published, unretracted incidents and active vaults on served chains qualify. An empty response can therefore mean no qualifying incident in that window; it does not prove there have been no incidents. The host health monitor alerts if the unfiltered list remains empty for more than 24 hours.

Query Parameters

ParameterTypeDefaultDescription
limitnumber100Max vaults (1–500). Anonymous callers are capped at 50
affectsProtocolScorebooleanfalsetrue keeps only incidents flagged as affecting the protocol score, vault-linked ones included (the list as served before 2026-09-11)

Example

curl https://api.philidor.io/v1/vaults/with-critical-incidents?limit=2

Response

{
  "data": [
    {
      "vault_id": "morpho-1-0x8eb67a509616cd6a7c1b3c8c21d48ff57df3d458",
      "vault_name": "Resolv USDC",
      "chain_name": "Ethereum",
      "protocol_name": "Yearn",
      "curator_name": null,
      "tvl_usd": 5000000,
      "last_incident_at": "2025-11-30T00:00:00Z",
      "days_since_incident": 87,
      "incident_title": "yETH Stableswap Pool exploit",
      "incident_severity": "major",
      "event_severity": "Critical"
    }
  ]
}

On this page

Raw