# Comparable rates for one pair

Endpoint reference for the 1delta API. Index: https://docs.1delta.io/llms.txt · every endpoint: https://docs.1delta.io/llms-full.txt

---

### GET /v1/data/lending/comparables

- operationId: `comparable-rates-for-one-pair`
- docs: https://docs.1delta.io/1delta-api/comparable-rates-for-one-pair/
- markdown: https://docs.1delta.io/1delta-api/comparable-rates-for-one-pair.md
- tags: Lending (Data)

Comparable rates for one pair

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 band** — `floor = 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.

**Parameters**

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `chainId` | query | string | no | Single 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. |
| `chainIds` | query | string | no | CSV of chain IDs. When two or more, asset filters operate on asset groups. See the `ChainId` schema for the full set of supported chains. |
| `side` | query | "borrow" \| "supply" | no | Which leg to compare. |
| `debt` | query | string | no | The borrowed asset — address (single chain) or asset group. Required for `side=borrow`. Aliases: `debts`, `debtGroups`. |
| `debtGroups` | query | string | no | CSV of debt-side asset groups (works in any chain mode). |
| `collateral` | query | string | no | The 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`. |
| `collateralGroups` | query | string | no | CSV of collateral-side asset groups (works in any chain mode). |
| `amount` | query | number | no | Notional in TOKEN UNITS of the priced leg (the debt asset for a borrow). Requires exactly one asset on that side. |
| `amountUsd` | query | number | no | Same notional in USD. Works with multi-asset selections. Mutually exclusive with `amount`. |
| `horizonDays` | query | number | no | Holding period every venue is repriced to. At 365 a floating pool’s effective rate equals its sticker rate. |
| `rateType` | query | "all" \| "fixed" \| "float" | no | Post-filter applied before dedupe and ranking. |
| `limit` | query | integer | no | How many comparables to return (max 25). |
| `referenceMarketUid` | query | string | no | The venue the caller is already on. Returned as `reference` and excluded from `items`, so the pill can render "you: X% · best: Y%". |
| `referenceTermId` | query | string | no | Disambiguates which term of the reference market is the caller’s own. |
| `includeUnobtainable` | query | boolean | no | Keep quotes that cannot be taken right now (closed Term auction rounds), flagged and ranked last. |
| `minLiquidityRatio` | query | number | no | Ratio side of the depth floor `min(ratio × anchor, cap)`. |
| `liquidityFloorCapUsd` | query | number | no | Cap side of the depth floor — stops a huge anchor from demanding a comparably huge venue and returning nothing. |
| `minLiquidityUsd` | query | number | no | Absolute USD depth floor that REPLACES the computed `min(ratio × anchor, cap)`. |
| `includeStale` | query | boolean | no | Rank venues whose market data is older than the freshness window. Excluded but counted (`droppedStale`) by default. |
| `includeIlliquid` | query | boolean | no | Disable the depth band entirely and rank every venue regardless of size. |
| `collateralPreference` | query | string | no | CSV of collateral asset groups tried in order when picking the comparison basis (only used when no collateral is pinned). |
| `lenders` | query | string | no | CSV of lender keys (prefix-expanded) to restrict the comparison to. See the `LenderId` schema for the full set of accepted values. |
| `excludeLenders` | query | string | no | CSV of lender keys to exclude (prefix-expanded). |
| `maxConfigRiskScore` | query | number | no | Config-risk ceiling (default 4, as on the pair endpoints). |
| `maxTokenRiskScore` | query | number | no | Token-risk ceiling across both legs. |

**Response `200`** — Comparable rates

| Field | Type | Description |
| --- | --- | --- |
| `success` | true |  |
| `data` | object |  |
| `data.side` | "borrow" \| "supply" |  |
| `data.horizonDays` | number |  |
| `data.amount` | number |  |
| `data.amountUsd` | number |  |
| `data.chainIds` | string[] | EVM chain ids, as decimal strings. See the `ChainId` schema. |
| `data.scanned` | integer | Pair rows considered before ranking. |
| `data.truncated` | boolean | The candidate guard cap bound, so ranking saw only the deepest rows — narrow the filter. Never a silent trim. |
| `data.available` | integer | Distinct comparables that existed before `limit` was applied. |
| `data.liquidityFloorUsd` | number | Venues below this USD depth were dropped as not comparable (0 = no floor applied). |
| `data.droppedIlliquid` | integer | How many venues the depth band removed. An opinionated filter must never be silent — surface this rather than implying nothing else exists. |
| `data.droppedStale` | integer | Venues 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.staleMaxHours` | number | The freshness window `droppedStale` was measured against. |
| `data.collateralBasis` | object | The 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.reference` | object | The venue the caller is already on (`referenceMarketUid`), pulled out of `items` so the UI can render "you: X% · best: Y%". |
| `data.reference.rank` | integer | 1-based position in the ranking. `0` on the `reference` item. |
| `data.reference.chainId` | string | EVM chain id, as a decimal string. See the `ChainId` schema. |
| `data.reference.lender` | string | Raw 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.reference.lenderName` | string | Display name from the lender registry, falling back to the raw key when none is registered. This is what a UI should render. |
| `data.reference.lenderLogoUri` | string |  |
| `data.reference.marketUid` | string | The market the rate belongs to: the debt market for a borrow quote, the collateral market for a supply quote. |
| `data.reference.marketName` | string |  |
| `data.reference.curatorName` | string |  |
| `data.reference.eMode` | string |  |
| `data.reference.rateType` | "fixed" \| "float" |  |
| `data.reference.rateModel` | "variable" \| "lista" \| "midnight" \| "term" \| "exactly" \| "teller" \| … (9 values) | Which repayment rule priced this quote. |
| `data.reference.aprPct` | number | Sticker 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.reference.aprAtAmountPct` | number | The 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.reference.rewardAprPct` | number | Reward emissions folded into the headline (positive = subsidising a borrow). Large emissions can push a borrow rate NEGATIVE. |
| `data.reference.aprExRewardsPct` | number | The rate WITHOUT reward emissions — the structural, sustainable cost. Show it next to a reward-inflated headline; emissions are transient. |
| `data.reference.effectiveAprPct` | number | Size-priced rate normalized to `horizonDays`. The ranking number. |
| `data.reference.costPct` | number | Total cost over the horizon as a percent of principal. |
| `data.reference.horizon` | object | How the effective rate was arrived at — the caveats a UI must show next to a normalized number. |
| `data.reference.horizon.basis` | "flat-forward" \| "early-exit" \| "held-to-maturity" \| "rolled" | `flat-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.reference.horizon.locked` | boolean | Is 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.reference.horizon.priceRisk` | boolean | Exiting 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.reference.horizon.assumptions` | string[] | Display-ready caveats, most important first. |
| `data.reference.termId` | string |  |
| `data.reference.durationDays` | number |  |
| `data.reference.maturity` | number | Calendar maturity, unix seconds. Null for rolling menus and variable pools. |
| `data.reference.termDays` | number | Remaining term in days for a position opened now. |
| `data.reference.obtainable` | boolean | Can this rate be taken right now? False for a Term repo between auction rounds, whose rate card still quotes the last round’s clearing rate. |
| `data.reference.obtainableReason` | string |  |
| `data.reference.quoteBasis` | "live" \| "last-clearing" | `last-clearing` marks a historical print, not an obtainable quote — never render it as an actionable rate. |
| `data.reference.depth` | object |  |
| `data.reference.depth.fillable` | number | Depth ceiling in token units of the priced leg — borrow liquidity for a pool, total book depth for an order book. |
| `data.reference.depth.capped` | boolean | The requested amount exceeds `fillable`: this venue cannot fund the position at that size, and the row is demoted below every non-capped one. |
| `data.reference.depth.liquidityUsd` | number |  |
| `data.reference.depth.utilization` | number | Market utilization, as a fraction between 0 and 1. |
| `data.reference.collateral` | object |  |
| `data.reference.collateral.address` | string | EVM contract address, lowercase or checksummed hex. |
| `data.reference.collateral.assetGroup` | string | Group of economically equivalent assets, e.g. all USDC variants. |
| `data.reference.collateral.symbol` | string | Token symbol, e.g. `WETH`. |
| `data.reference.collateral.decimals` | number | Token decimals — divide raw amounts by `10 ** decimals`. |
| `data.reference.collateral.logoUri` | string | URL of the logo image. |
| `data.reference.collateral.marketUid` | string | Market identifier, formatted `lender:chainId:address`. |
| `data.reference.debt` | object |  |
| `data.reference.debt.address` | string | EVM contract address, lowercase or checksummed hex. |
| `data.reference.debt.assetGroup` | string | Group of economically equivalent assets, e.g. all USDC variants. |
| `data.reference.debt.symbol` | string | Token symbol, e.g. `WETH`. |
| `data.reference.debt.decimals` | number | Token decimals — divide raw amounts by `10 ** decimals`. |
| `data.reference.debt.logoUri` | string | URL of the logo image. |
| `data.reference.debt.marketUid` | string | Market identifier, formatted `lender:chainId:address`. |
| `data.reference.maxLeverage` | number | Highest leverage multiple reachable in this market. |
| `data.reference.ltv` | number | Loan-to-value ratio, as a fraction between 0 and 1. |
| `data.reference.risk` | object |  |
| `data.reference.risk.configScore` | number |  |
| `data.reference.risk.maxTokenScore` | number |  |
| `data.count` | integer | Number of entries in `items`. |
| `data.items` | object[] | The result set for this response. |
| `data.items[].rank` | integer | 1-based position in the ranking. `0` on the `reference` item. |
| `data.items[].chainId` | string | EVM chain id, as a decimal string. See the `ChainId` schema. |
| `data.items[].lender` | string | Raw 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[].lenderName` | string | Display name from the lender registry, falling back to the raw key when none is registered. This is what a UI should render. |
| `data.items[].lenderLogoUri` | string |  |
| `data.items[].marketUid` | string | The market the rate belongs to: the debt market for a borrow quote, the collateral market for a supply quote. |
| `data.items[].marketName` | string |  |
| `data.items[].curatorName` | string |  |
| `data.items[].eMode` | string |  |
| `data.items[].rateType` | "fixed" \| "float" |  |
| `data.items[].rateModel` | "variable" \| "lista" \| "midnight" \| "term" \| "exactly" \| "teller" \| … (9 values) | Which repayment rule priced this quote. |
| `data.items[].aprPct` | number | Sticker 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[].aprAtAmountPct` | number | The 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[].rewardAprPct` | number | Reward emissions folded into the headline (positive = subsidising a borrow). Large emissions can push a borrow rate NEGATIVE. |
| `data.items[].aprExRewardsPct` | number | The rate WITHOUT reward emissions — the structural, sustainable cost. Show it next to a reward-inflated headline; emissions are transient. |
| `data.items[].effectiveAprPct` | number | Size-priced rate normalized to `horizonDays`. The ranking number. |
| `data.items[].costPct` | number | Total cost over the horizon as a percent of principal. |
| `data.items[].horizon` | object | How the effective rate was arrived at — the caveats a UI must show next to a normalized number. |
| `data.items[].horizon.basis` | "flat-forward" \| "early-exit" \| "held-to-maturity" \| "rolled" | `flat-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.locked` | boolean | Is 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.priceRisk` | boolean | Exiting 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.assumptions` | string[] | Display-ready caveats, most important first. |
| `data.items[].termId` | string |  |
| `data.items[].durationDays` | number |  |
| `data.items[].maturity` | number | Calendar maturity, unix seconds. Null for rolling menus and variable pools. |
| `data.items[].termDays` | number | Remaining term in days for a position opened now. |
| `data.items[].obtainable` | boolean | Can this rate be taken right now? False for a Term repo between auction rounds, whose rate card still quotes the last round’s clearing rate. |
| `data.items[].obtainableReason` | string |  |
| `data.items[].quoteBasis` | "live" \| "last-clearing" | `last-clearing` marks a historical print, not an obtainable quote — never render it as an actionable rate. |
| `data.items[].depth` | object |  |
| `data.items[].depth.fillable` | number | Depth ceiling in token units of the priced leg — borrow liquidity for a pool, total book depth for an order book. |
| `data.items[].depth.capped` | boolean | The requested amount exceeds `fillable`: this venue cannot fund the position at that size, and the row is demoted below every non-capped one. |
| `data.items[].depth.liquidityUsd` | number |  |
| `data.items[].depth.utilization` | number | Market utilization, as a fraction between 0 and 1. |
| `data.items[].collateral` | object |  |
| `data.items[].collateral.address` | string | EVM contract address, lowercase or checksummed hex. |
| `data.items[].collateral.assetGroup` | string | Group of economically equivalent assets, e.g. all USDC variants. |
| `data.items[].collateral.symbol` | string | Token symbol, e.g. `WETH`. |
| `data.items[].collateral.decimals` | number | Token decimals — divide raw amounts by `10 ** decimals`. |
| `data.items[].collateral.logoUri` | string | URL of the logo image. |
| `data.items[].collateral.marketUid` | string | Market identifier, formatted `lender:chainId:address`. |
| `data.items[].debt` | object |  |
| `data.items[].debt.address` | string | EVM contract address, lowercase or checksummed hex. |
| `data.items[].debt.assetGroup` | string | Group of economically equivalent assets, e.g. all USDC variants. |
| `data.items[].debt.symbol` | string | Token symbol, e.g. `WETH`. |
| `data.items[].debt.decimals` | number | Token decimals — divide raw amounts by `10 ** decimals`. |
| `data.items[].debt.logoUri` | string | URL of the logo image. |
| `data.items[].debt.marketUid` | string | Market identifier, formatted `lender:chainId:address`. |
| `data.items[].maxLeverage` | number | Highest leverage multiple reachable in this market. |
| `data.items[].ltv` | number | Loan-to-value ratio, as a fraction between 0 and 1. |
| `data.items[].risk` | object |  |
| `actions` | null |  |

**Example response**

```json
{
  "success": true,
  "data": {
    "side": "borrow",
    "horizonDays": 1,
    "amount": 1,
    "amountUsd": 1,
    "chainIds": [
      "1"
    ],
    "scanned": 1,
    "truncated": true,
    "available": 1,
    "liquidityFloorUsd": 1,
    "droppedIlliquid": 1,
    "droppedStale": 1,
    "staleMaxHours": 1,
    "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,
      "aprAtAmountPct": 1,
      "rewardAprPct": 1,
      "aprExRewardsPct": 1,
      "effectiveAprPct": 1,
      "costPct": 1,
      "horizon": {
        "basis": "flat-forward",
        "locked": true,
        "priceRisk": true,
        "assumptions": [
          "string"
        ]
      },
      "termId": "string",
      "durationDays": 1,
      "maturity": 1,
      "termDays": 1,
      "obtainable": true,
      "obtainableReason": "string",
      "quoteBasis": "live",
      "depth": {
        "fillable": 1,
        "capped": true,
        "liquidityUsd": 1,
        "utilization": 1
      },
      "collateral": {
        "address": "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2",
        "assetGroup": "string",
        "symbol": "string",
        "decimals": 1,
        "logoUri": "string",
        "marketUid": "AAVE_V3:1:0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2"
      },
      "debt": {
        "address": "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2",
        "assetGroup": "string",
        "symbol": "string",
        "decimals": 1,
        "logoUri": "string",
        "marketUid": "AAVE_V3:1:0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2"
      },
      "maxLeverage": 1,
      "ltv": 1,
      "risk": {
        "configScore": 1,
        "maxTokenScore": 1
      }
    },
    "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,
        "aprAtAmountPct": 1,
        "rewardAprPct": 1,
        "aprExRewardsPct": 1,
        "effectiveAprPct": 1,
        "costPct": 1,
        "horizon": {
          "basis": "flat-forward",
          "locked": true,
          "priceRisk": true,
          "assumptions": [
            "string"
          ]
        },
        "termId": "string",
        "durationDays": 1,
        "maturity": 1,
        "termDays": 1,
        "obtainable": true,
        "obtainableReason": "string",
        "quoteBasis": "live",
        "depth": {
          "fillable": 1,
          "capped": true,
          "liquidityUsd": 1,
          "utilization": 1
        },
        "collateral": {
          "address": "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2",
          "assetGroup": "string",
          "symbol": "string",
          "decimals": 1,
          "logoUri": "string",
          "marketUid": "AAVE_V3:1:0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2"
        },
        "debt": {
          "address": "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2",
          "assetGroup": "string",
          "symbol": "string",
          "decimals": 1,
          "logoUri": "string",
          "marketUid": "AAVE_V3:1:0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2"
        },
        "maxLeverage": 1,
        "ltv": 1,
        "risk": {
          "configScore": 1,
          "maxTokenScore": 1
        }
      }
    ]
  }
}
```

**Response `400`** — Validation error

**Response `429`** — Rate limited. Unauthenticated callers share a per-IP budget; send an `x-api-key` header to lift it. Retry with exponential backoff.

**Response `500`** — Unexpected server error. Safe to retry with backoff.

**Response `502`** — An upstream data source or protocol origin failed (`error.code` is `ORIGIN_FAILED`). `error.details` carries the per-origin status. This is also what a missing or malformed required parameter currently returns, rather than a 400.
