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
| Parameter | Type | Default | Description |
|---|---|---|---|
| page | number | 1 | Page number |
| limit | number | 20 | Results per page, max 100 |
| chain | string | — | Filter by chain (comma-separated: ethereum,base) |
| protocol | string | — | Filter by protocol ID (comma-separated: morpho,aave) |
| curator | string | — | Filter by curator ID (comma-separated) |
| asset | string | — | Filter by asset symbol (comma-separated: USDC,WETH) |
| riskTier | string | — | Filter by risk tier (comma-separated, case-insensitive: prime, core, edge) |
| minRiskScore | number | — | Minimum risk score (0–10, inclusive). Vaults without a score are excluded. |
| maxRiskScore | number | — | Maximum risk score (0–10, inclusive). Vaults without a score are excluded. |
| minScore | number | — | Alias of minRiskScore (vaults.fyi-style, 0–10). See below. |
| min_score | number | — | snake_case alias of minScore. |
| search | string | — | Search vault name, symbol, asset, protocol, curator |
| minTvl | string | — | Minimum TVL in USD |
| stablecoin | string | — | Filter stablecoin vaults (true/false) |
| lsd | string | — | Filter LSD vaults (true/false) |
| singleExposure | string | — | Filter single-exposure vaults (true/false) |
| noIL | string | — | Filter no impermanent loss (true/false) |
| audited | string | — | Filter audited vaults (true/false) |
| highConfidence | string | — | Filter high-confidence vaults (true/false) |
| depositable | string | — | Filter by current deposit capacity (true/false). See below. |
| sortBy | string | — | 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 |
| sortOrder | string | desc | Sort 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→ returnsopenandunknownvaults — 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 onlyclosedvaults (confirmedmaxDeposit == 0on 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.fyi | Philidor equivalent |
|---|---|
minVaultScore=80 | minScore=8.0 or riskTier=prime |
minVaultScore=70 | minScore=7.0 or riskTier=prime,core |
minVaultScore=50 | minScore=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:
| Status | Meaning |
|---|---|
available | Available liquidity is positive and the deficit is zero. |
exhausted | Available liquidity and deficit are both zero. |
deficit | Raw utilization exceeds 100%; available liquidity is zero and the deficit is positive when TVL is positive. |
unknown | TVL 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.
| Status | Meaning |
|---|---|
open | Source-backed protocol state permits new borrowing. |
disabled | Borrowing is not enabled for this reserve. |
paused | A protocol pause or reserve freeze blocks borrowing. |
capped | An observed hard borrow cap has no remaining capacity. |
unknown | Philidor cannot currently prove the borrow permission. |
not_applicable | The 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_aprAll 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:
| Status | Meaning |
|---|---|
observed | One or more active borrower incentive campaigns were observed. |
none | The incentive source was checked successfully and no campaign was active. |
unavailable | Philidor could not establish the current incentive state; net APR is null. |
not_applicable | The 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
| Parameter | Type | Description |
|---|---|---|
| id | string | Vault ID (e.g., morpho-1-0x...) |
Example
curl https://api.philidor.io/v1/vaults/morpho-1-0x8eb67a509616cd6a7c1b3c8c21d48ff57df3d458Returns full vault detail with risk vectors, snapshots, events, and rewards. Same schema as vault detail.
Query Parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
| points | number | 360 | Snapshot points embedded in the detail response, max 2160 |
| resolution | number | 360 | Compatibility 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_scoreat least 7.5 and net APR between 3% and 10%. A Prime vault with a lower or higher yield isfalse.tvl_status:observedwhentvl_usdis a read value;unavailablewhen the TVL read failed andtvl_usdis served as0for 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
| Parameter | Type | Default | Description |
|---|---|---|---|
| since | string | required | ISO timestamp or cursor from the previous response |
| limit | number | 100 | Results per request, max 500 |
| changed | string | sync | Which 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
| Parameter | Type | Default | Description |
|---|---|---|---|
| limit | number | 100 | Max vaults (1–500). Anonymous callers are capped at 50 |
| affectsProtocolScore | boolean | false | true 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=2Response
{
"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"
}
]
}Access Matrix
Every public route with the credential it needs, the plan that unlocks it, the rate bucket it is metered on, and the outcome per plan. Generated from the API route policy.
Vault Detail
GET /v1/vault for full risk breakdown, historical snapshots, score history, holders, events, Morpho markets, and strategy detail.