Skip to main content

Comparable rates for one pair

GET 

/v1/data/lending/comparables

The N best comparable rates for a single pair, priced at the caller’s size and holding period. One round-trip, small payload — built to sit behind a rate pill next to a borrow input.

Unlike /pairs/optimize (rank every leveraged pair), this answers the question a borrow form asks: I am about to borrow X of asset D against collateral C — what are the best rates for that same trade, at my size, over my horizon?

Three rates, deliberately kept apart

  • aprPct — what the venue advertises: spot at 0 notional, or top of book.
  • aprAtAmountPct — the same rate at your size. A utilization pool re-prices its whole balance to the post-borrow rate (marginal == average); an order book fills cheapest-first, so this is the VWAP of the tiers consumed. Null without an amount.
  • effectiveAprPct — the size-priced rate normalized to horizonDays. This is what rank sorts on.

Horizon normalization

A 4-week fixed rate and a floating pool are not comparable until both are priced over the same holding period. Each venue supplies a repayment rule: a Lista loan exited early pays roughly half the remaining term’s interest (r·(T+h)/(2h) — 1%/1yr exited at 6 months is 1.5% effective); Midnight/Term charge no penalty but exiting means a resale at the then-current price (horizon.priceRisk); Exactly rebates unassigned earnings; a CDP’s one-time origination fee amortizes over the horizon, so it dominates on short holds. A horizon that outlasts the term assumes a roll at today’s rate and reports locked: false. Always render horizon.locked and horizon.assumptions next to the number — a normalized rate is not a contractual one.

Obtainability is part of the answer

Term Finance originates only inside periodic sealed-bid rounds; between rounds its rate card still quotes the LAST round’s clearing rate — real, but not takeable. Those rows are dropped by default and, with includeUnobtainable=true, returned flagged (obtainable: false, quoteBasis: "last-clearing") and ranked last. A venue that cannot fund the requested size is marked depth.capped and demoted rather than ranked on a rate it can’t honour.

Comparability filters (what makes this a comparison rather than a list)

Depth bandfloor = min(minLiquidityRatio × anchor, liquidityFloorCapUsd), default min(50% × anchor, $10M). A venue an order of magnitude shallower is a different product, and it is exactly those markets that quote absurd rates — a near-empty isolated market with reward emissions prices borrowing at −25%, a real number and a useless comparison against a $5B pool. The cap is not optional: a pure ratio breaks on the biggest pairs, where half of a $5B pool is a $2.5B floor nothing clears, so the comparison would come back empty exactly where alternatives matter most. The anchor is referenceMarketUid's own depth when given, else the deepest candidate. droppedIlliquid + liquidityFloorUsd report it — the filter is never silent; minLiquidityUsd replaces the formula and includeIlliquid=true turns it off. Stale venues. A lender whose ingest lagged past the freshness window drops out of the underlying pairs view — which reads exactly like "that lender doesn't offer this pair". Those rows are excluded from ranking but counted (droppedStale, staleMaxHours), because a $19B deployment vanishing with no signal is worse than a short list. includeStale=true ranks them anyway. One collateral basis. When the caller pins no collateral (a standalone borrow form knows none), comparing "borrow USDC" across every collateral in existence is not a comparison. The server picks ONE representative collateral per chain — first hit in collateralPreference (default ETH,BTC,USDC,USDT), else the collateral group backing the deepest markets, which resolves to the wrapped native where neither is listed — and compares only venues backed by it. The choice comes back as collateralBasis (e.g. {"1":"ETH"}) so the UI can say "vs WETH-backed". An explicit collateral/collateralGroups always wins and disables this.

Rewards

The headline rate folds in reward emissions (same composition as /pairs/optimize), which is why a borrow rate can be negative. That is real but transient, so each row also carries rewardAprPct and aprExRewardsPct — the structural cost without emissions. Ranking stays on the headline; showing both is the caller's job.

Ranking

obtainable first, then non-capped, then best effectiveAprPct (cheapest for a borrow, highest for a supply), then deepest liquidity as tie-break. Results are deduped to one row per (chain, lender, market, term) — the pairs view is combinatorial in the other leg, so without it the "5 best rates" would be five copies of the same market.

Plain-text reference — GET /v1/data/lending/comparables

Parameters

ParameterInTypeRequiredDescription
chainIdquerystringnoSingle chain ID. When set, asset filters operate on token addresses. Pass one for an actionable list — cross-chain rows are a comparison, not one executable position. See the ChainId schema for the full set of supported chains.
chainIdsquerystringnoCSV of chain IDs. When two or more, asset filters operate on asset groups. See the ChainId schema for the full set of supported chains.
sidequeryborrow, supplynoWhich leg to compare.
debtquerystringnoThe borrowed asset — address (single chain) or asset group. Required for side=borrow. Aliases: debts, debtGroups.
debtGroupsquerystringnoCSV of debt-side asset groups (works in any chain mode).
collateralquerystringnoThe collateral asset. Required for side=supply; strongly recommended for side=borrow — a market that will not take your collateral is not a comparable. Aliases: collaterals, collateralGroups.
collateralGroupsquerystringnoCSV of collateral-side asset groups (works in any chain mode).
amountquerynumbernoNotional in TOKEN UNITS of the priced leg (the debt asset for a borrow). Requires exactly one asset on that side.
amountUsdquerynumbernoSame notional in USD. Works with multi-asset selections. Mutually exclusive with amount.
horizonDaysquerynumbernoHolding period every venue is repriced to. At 365 a floating pool’s effective rate equals its sticker rate.
rateTypequeryall, fixed, floatnoPost-filter applied before dedupe and ranking.
limitqueryintegernoHow many comparables to return (max 25).
referenceMarketUidquerystringnoThe venue the caller is already on. Returned as reference and excluded from items, so the pill can render "you: X% · best: Y%".
referenceTermIdquerystringnoDisambiguates which term of the reference market is the caller’s own.
includeUnobtainablequerybooleannoKeep quotes that cannot be taken right now (closed Term auction rounds), flagged and ranked last.
minLiquidityRatioquerynumbernoRatio side of the depth floor min(ratio × anchor, cap).
liquidityFloorCapUsdquerynumbernoCap side of the depth floor — stops a huge anchor from demanding a comparably huge venue and returning nothing.
minLiquidityUsdquerynumbernoAbsolute USD depth floor that REPLACES the computed min(ratio × anchor, cap).
includeStalequerybooleannoRank venues whose market data is older than the freshness window. Excluded but counted (droppedStale) by default.
includeIlliquidquerybooleannoDisable the depth band entirely and rank every venue regardless of size.
collateralPreferencequerystringnoCSV of collateral asset groups tried in order when picking the comparison basis (only used when no collateral is pinned).
lendersquerystringnoCSV of lender keys (prefix-expanded) to restrict the comparison to. See the LenderId schema for the full set of accepted values.
excludeLendersquerystringnoCSV of lender keys to exclude (prefix-expanded).
maxConfigRiskScorequerynumbernoConfig-risk ceiling (default 4, as on the pair endpoints).
maxTokenRiskScorequerynumbernoToken-risk ceiling across both legs.

Response 200

FieldTypeDescription
successTrue
dataobject
data.sideborrow, supply
data.horizonDaysnumber
data.amountnumber
data.amountUsdnumber
data.chainIdsstring[]EVM chain ids, as decimal strings. See the ChainId schema.
data.scannedintegerPair rows considered before ranking.
data.truncatedbooleanThe candidate guard cap bound, so ranking saw only the deepest rows — narrow the filter. Never a silent trim.
data.availableintegerDistinct comparables that existed before limit was applied.
data.liquidityFloorUsdnumberVenues below this USD depth were dropped as not comparable (0 = no floor applied).
data.droppedIlliquidintegerHow many venues the depth band removed. An opinionated filter must never be silent — surface this rather than implying nothing else exists.
data.droppedStaleintegerVenues excluded because their market data is older than staleMaxHours — an ingest lag, not an absent lender. Non-zero is the difference between "no one else offers this pair" and "we cannot currently see who does". includeStale=true ranks them anyway.
data.staleMaxHoursnumberThe freshness window droppedStale was measured against.
data.collateralBasisobjectThe single collateral group each chain's rows were compared against when the caller pinned none, e.g. {"1":"ETH"}. Empty when a collateral was supplied, or on the supply side.
data.referenceobjectThe venue the caller is already on (referenceMarketUid), pulled out of items so the UI can render "you: X% · best: Y%".
data.countintegerNumber of entries in items.
data.itemsobject[]The result set for this response.
data.items[].rankinteger1-based position in the ranking. 0 on the reference item.
data.items[].chainIdstringEVM chain id, as a decimal string. See the ChainId schema.
data.items[].lenderstringRaw lender key — stable, use it for keying and deeplinks. Per-market lenders (Morpho Blue, Silo, Euler) encode a hashed market id here, so it is NOT presentable.
data.items[].lenderNamestringDisplay name from the lender registry, falling back to the raw key when none is registered. This is what a UI should render.
data.items[].lenderLogoUristring
data.items[].marketUidstringThe market the rate belongs to: the debt market for a borrow quote, the collateral market for a supply quote.
data.items[].marketNamestring
data.items[].curatorNamestring
data.items[].eModestring
data.items[].rateTypefixed, float
data.items[].rateModelvariable, lista, midnight, term, exactly, teller, … (9 values)Which repayment rule priced this quote.
data.items[].aprPctnumberSticker rate, APR %. Includes intrinsic yield and reward APR (same composition as borrowAprShort / depositAprLong); a one-time origination fee is NOT in it — the horizon model amortizes that separately.
data.items[].aprAtAmountPctnumberThe rate at the requested size. Null when no amount was supplied. A utilization pool re-prices its whole balance to the post-action rate; an order book fills cheapest-first, so this is the VWAP of the tiers consumed.
data.items[].rewardAprPctnumberReward emissions folded into the headline (positive = subsidising a borrow). Large emissions can push a borrow rate NEGATIVE.
data.items[].aprExRewardsPctnumberThe rate WITHOUT reward emissions — the structural, sustainable cost. Show it next to a reward-inflated headline; emissions are transient.
data.items[].effectiveAprPctnumberSize-priced rate normalized to horizonDays. The ranking number.
data.items[].costPctnumberTotal cost over the horizon as a percent of principal.
data.items[].horizonobjectHow the effective rate was arrived at — the caveats a UI must show next to a normalized number.
data.items[].horizon.basisflat-forward, early-exit, held-to-maturity, rolledflat-forward: no maturity, today’s floating rate assumed to hold. early-exit: horizon shorter than the term, the venue’s exit rule applied. held-to-maturity: horizon matches the term (the clean case). rolled: horizon outlasts the term, a roll at today’s rate assumed.
data.items[].horizon.lockedbooleanIs the rate contractually fixed for the WHOLE horizon? False for floating pools and for a fixed term that has to be rolled to cover the horizon.
data.items[].horizon.priceRiskbooleanExiting early means unwinding on an order book at the then-current price (Midnight/Term), so the realized cost can land either side of the quote. Not priced in — flagged.
data.items[].horizon.assumptionsstring[]Display-ready caveats, most important first.

Example response

{
"success": true,
"data": {
"side": "borrow",
"horizonDays": 1.0,
"amount": 1.0,
"amountUsd": 1.0,
"chainIds": [
"1"
],
"scanned": 1,
"truncated": true,
"available": 1,
"liquidityFloorUsd": 1.0,
"droppedIlliquid": 1,
"droppedStale": 1,
"staleMaxHours": 1.0,
"collateralBasis": {},
"reference": {
"rank": 1,
"chainId": "1",
"lender": "AAVE_V3",
"lenderName": "AAVE_V3",
"lenderLogoUri": "AAVE_V3",
"marketUid": "AAVE_V3:1:0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2",
"marketName": "string",
"curatorName": "string",
"eMode": "string",
"rateType": "fixed",
"rateModel": "variable",
"aprPct": 1.0,
"aprAtAmountPct": 1.0,
"rewardAprPct": 1.0,
"aprExRewardsPct": 1.0,
"effectiveAprPct": 1.0,
"costPct": 1.0,
"horizon": {
"basis": "flat-forward",
"locked": true,
"priceRisk": true,
"assumptions": [
"string"
]
},
"termId": "string",
"durationDays": 1.0,
"maturity": 1.0,
"termDays": 1.0,
"obtainable": true,
"obtainableReason": "string",
"quoteBasis": "live",
"depth": {
"fillable": 1.0,
"capped": true,
"liquidityUsd": 1.0,
"utilization": 1.0
},
"collateral": {
"address": "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2",
"assetGroup": "string",
"symbol": "string",
"decimals": 1.0,
"logoUri": "string",
"marketUid": "AAVE_V3:1:0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2"
},
"debt": {
"address": "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2",
"assetGroup": "string",
"symbol": "string",
"decimals": 1.0,
"logoUri": "string",
"marketUid": "AAVE_V3:1:0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2"
},
"maxLeverage": 1.0,
"ltv": 1.0,
"risk": {
"configScore": 1.0,
"maxTokenScore": 1.0
}
},
"count": 1,
"items": [
{
"rank": 1,
"chainId": "1",
"lender": "AAVE_V3",
"lenderName": "AAVE_V3",
"lenderLogoUri": "AAVE_V3",
"marketUid": "AAVE_V3:1:0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2",
"marketName": "string",
"curatorName": "string",
"eMode": "string",
"rateType": "fixed",
"rateModel": "variable",
"aprPct": 1.0,
"aprAtAmountPct": 1.0,
"rewardAprPct": 1.0,
"aprExRewardsPct": 1.0,
"effectiveAprPct": 1.0,
"costPct": 1.0,
"horizon": {
"basis": "flat-forward",
"locked": true,
"priceRisk": true,
"assumptions": [
"string"
]
},
"termId": "string",
"durationDays": 1.0,
"maturity": 1.0,
"termDays": 1.0,
"obtainable": true,
"obtainableReason": "string",
"quoteBasis": "live",
"depth": {
"fillable": 1.0,
"capped": true,
"liquidityUsd": 1.0,
"utilization": 1.0
},
"collateral": {
"address": "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2",
"assetGroup": "string",
"symbol": "string",
"decimals": 1.0,
"logoUri": "string",
"marketUid": "AAVE_V3:1:0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2"
},
"debt": {
"address": "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2",
"assetGroup": "string",
"symbol": "string",
"decimals": 1.0,
"logoUri": "string",
"marketUid": "AAVE_V3:1:0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2"
},
"maxLeverage": 1.0,
"ltv": 1.0,
"risk": {
"configScore": 1.0,
"maxTokenScore": 1.0
}
}
]
}
}

Request

Responses

Comparable rates