{
  "openapi": "3.1.0",
  "info": {
    "title": "AtlasYield API",
    "version": "1.0",
    "description": "Read-only access to the Atlas Score Index: neutral 16-factor vault scores across four pillars — Yield, Safety, Liquidity, Sustainability — plus the tracked vault catalog and the Atlas Engine, which turns those scores into a risk-adjusted allocation. Research and information, not investment advice.\nEvery endpoint in this spec is public and keyless, limited to 60 requests per minute per IP. An optional partner key sent in the `x-api-key` header moves the caller to its own bucket of 100 requests per minute and unlocks two endpoints that are not part of this spec (every vault's factor breakdown in one call, and score history). A key is never required: an unknown or retired key sent to a public endpoint is ignored, and only the two partner endpoints reject it, with a 401. How to get one: https://docs.atlasyield.club/partner-access"
  },
  "servers": [
    {
      "url": "https://api.atlasyield.club/v1"
    }
  ],
  "security": [
    {},
    {
      "PartnerKey": []
    }
  ],
  "components": {
    "securitySchemes": {
      "PartnerKey": {
        "type": "apiKey",
        "in": "header",
        "name": "x-api-key",
        "description": "Optional. Not needed for any endpoint in this spec. Unlocks the partner-only endpoints and a 100 requests per minute bucket (public limit is 60 per IP)."
      }
    },
    "schemas": {
      "CompactScore": {
        "type": "object",
        "description": "Returned by GET /scores when fields=compact. About 410 bytes per row (a full Score is about 820). No pillar, factor-quality or admin blobs. Fetch /vaults/{chainId}/{address}/factors for the breakdown.",
        "properties": {
          "vaultId": {
            "type": "string",
            "example": "morpho:8453:0xbbb2"
          },
          "chainId": {
            "type": "integer",
            "example": 8453
          },
          "protocolId": {
            "type": "string",
            "example": "morpho"
          },
          "name": {
            "type": [
              "string",
              "null"
            ]
          },
          "asset": {
            "type": [
              "string",
              "null"
            ],
            "example": "USDC"
          },
          "composite": {
            "type": "number",
            "example": 80
          },
          "label": {
            "type": "string",
            "example": "Good"
          },
          "apy": {
            "type": [
              "number",
              "null"
            ],
            "description": "Live APY in percentage points."
          },
          "tvl": {
            "type": [
              "number",
              "null"
            ],
            "description": "Live TVL in USD."
          },
          "dataQuality": {
            "type": "string",
            "enum": [
              "sufficient",
              "partial",
              "fallback"
            ]
          },
          "routable": {
            "type": "boolean",
            "description": "True when the vault is in the live catalog and not flagged unroutable by the route screen. A vault missing from the live catalog is false (fail closed)."
          },
          "flags": {
            "type": "array",
            "description": "Cautions computed from fields we already hold. A flag is a prompt to look closer, never a verdict, and an empty list is not a statement of safety. thin_tvl (live TVL under $2M, or unknown); apy_outlier (live APY at least 10 and at least 3x the median of its chain and asset peers, with at least 5 peers); fallback_data and partial_data (dataQuality); stale_score (scored more than 8 hours ago).",
            "items": {
              "type": "string",
              "enum": [
                "thin_tvl",
                "apy_outlier",
                "fallback_data",
                "partial_data",
                "stale_score"
              ]
            }
          },
          "scoredAt": {
            "type": "string",
            "format": "date-time"
          },
          "pageUrl": {
            "type": "string",
            "description": "Citable vault page.",
            "example": "https://atlasyield.club/vaults/morpho/8453/0xbbb2"
          }
        }
      },
      "Score": {
        "type": "object",
        "properties": {
          "vaultId": {
            "type": "string",
            "description": "Stable identifier: protocol:chainId:address.",
            "example": "aave-v3:8453:0x4e65fe4dba92790696d040ac24aa414708f5c0ab"
          },
          "chainId": {
            "type": "integer",
            "description": "Chain id. EVM chains use their EIP-155 id (1 Ethereum, 8453 Base, 42161 Arbitrum); Solana is 792703809 (Relay's id).",
            "example": 8453
          },
          "protocolId": {
            "type": "string",
            "description": "Adapter/protocol slug. Currently one of beefy, yearn-v3, morpho, pendle, aave-v3, euler, etherfi, ethena, kamino. New adapters are added over time — treat this as an open set rather than a fixed enum.",
            "example": "aave-v3"
          },
          "name": {
            "type": "string",
            "nullable": true,
            "description": "Human-readable vault name, the same string /catalog reports, so no join is needed. Null only when the vault is absent from the live catalog cache.",
            "example": "Aave V3 USDC"
          },
          "onchainId": {
            "type": "string",
            "nullable": true,
            "description": "The vault address with its original case. vaultId is lowercased, which is lossless for 0x addresses but not for Solana base58, so use this field to look a vault up on-chain. Null only when the vault is absent from the live cache.",
            "example": "0x4e65fE4DbA92790696d040ac24Aa414708F5c0AB"
          },
          "adminTopology": {
            "type": "object",
            "nullable": true,
            "description": "AY-269: per-vault admin setup, read daily. Null when the vault is not screened (Aave, Pendle, EtherFi, Ethena, and BNB vaults) and scores on protocol metadata instead. `class` is one of immutable, timelock_long_multisig_strong (7+ day delay, a 3+ threshold multisig that is also a majority of signers), timelock_short_multisig_weak, multisig_only, eoa (a single admin key), or unknown. For an `eoa` vault, `curatorRating` decides the score directly: 40 when the curator is rated established, 10 when unknown or new; every class floors at 10. `roles` never carries an address, only kind (none, eoa, safe, dao, unrecognised) plus threshold/owners for a safe. `dao` is on-chain governance (a token-vote governor or Sky), scored like a strong multisig; a dao with `unverifiedGovernance` (Aragon, plugin not inspected) is scored like a weak one. `delaySeconds` is the delay a timelock or supervisor adds in front of that role, on top of the vault's own `timelockSeconds`.",
            "properties": {
              "class": {
                "type": "string",
                "enum": [
                  "immutable",
                  "timelock_long_multisig_strong",
                  "timelock_short_multisig_weak",
                  "multisig_only",
                  "eoa",
                  "unknown"
                ]
              },
              "curatorId": {
                "type": "string",
                "nullable": true
              },
              "curatorRating": {
                "type": "string",
                "nullable": true,
                "enum": [
                  "established",
                  "unknown_new"
                ]
              },
              "timelockSeconds": {
                "type": "number",
                "nullable": true
              },
              "roles": {
                "type": "array",
                "items": {
                  "type": "object",
                  "properties": {
                    "role": {
                      "type": "string"
                    },
                    "kind": {
                      "type": "string",
                      "enum": [
                        "none",
                        "eoa",
                        "safe",
                        "dao",
                        "unrecognised"
                      ]
                    },
                    "threshold": {
                      "type": "number"
                    },
                    "owners": {
                      "type": "number"
                    },
                    "delaySeconds": {
                      "type": "number"
                    },
                    "unverifiedGovernance": {
                      "type": "boolean"
                    }
                  }
                }
              },
              "checkedAt": {
                "type": "string",
                "format": "date-time"
              }
            }
          },
          "asset": {
            "type": "string",
            "description": "Underlying asset symbol.",
            "example": "USDC"
          },
          "assetClass": {
            "type": "string",
            "nullable": true,
            "enum": [
              "stablecoin",
              "stablecoin-yield",
              "eth",
              "btc",
              "top50",
              "usd-exotic",
              "other"
            ],
            "description": "Asset class bucket used to contextualise the yield factors. May be null.",
            "example": "stablecoin"
          },
          "composite": {
            "type": "number",
            "description": "0–100 composite Atlas Score.",
            "example": 87.5
          },
          "label": {
            "type": "string",
            "enum": [
              "Conservative",
              "Balanced",
              "Aggressive"
            ],
            "description": "Risk-posture band for the composite."
          },
          "pillarScores": {
            "type": "object",
            "description": "The four pillar scores, 0–100 each.",
            "properties": {
              "yield": {
                "type": "number",
                "example": 82
              },
              "safety": {
                "type": "number",
                "example": 90
              },
              "liquidity": {
                "type": "number",
                "example": 88
              },
              "sustainability": {
                "type": "number",
                "example": 85
              }
            }
          },
          "apy": {
            "type": "number",
            "nullable": true,
            "description": "Current APY in percentage points (5.21 = 5.21%). May be null.",
            "example": 5.21
          },
          "tvl": {
            "type": "number",
            "nullable": true,
            "description": "Total value locked, USD. May be null.",
            "example": 1250400
          },
          "dataQuality": {
            "type": "string",
            "enum": [
              "sufficient",
              "partial",
              "fallback"
            ],
            "description": "How complete the inputs were."
          },
          "scoredAt": {
            "type": "string",
            "format": "date-time",
            "description": "ISO-8601 UTC timestamp the score was computed (ex-ante, immutable)."
          },
          "scorerVersion": {
            "type": "string",
            "description": "Scoring engine version that produced the row.",
            "example": "2.2.0"
          }
        }
      },
      "CatalogEntry": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "description": "Vault identifier (same as vaultId in /scores).",
            "example": "aave-v3:8453:0x4e65fe4dba92790696d040ac24aa414708f5c0ab"
          },
          "chainId": {
            "type": "integer",
            "example": 8453
          },
          "protocolId": {
            "type": "string",
            "example": "aave-v3"
          },
          "protocol": {
            "type": "string",
            "description": "Human-readable protocol name.",
            "example": "Aave V3"
          },
          "asset": {
            "type": "string",
            "example": "USDC"
          },
          "name": {
            "type": "string",
            "description": "Display name (protocol + asset).",
            "example": "Aave V3 USDC"
          },
          "tvl": {
            "type": "number",
            "description": "Total value locked, USD.",
            "example": 1250400
          },
          "apy": {
            "type": "number",
            "description": "Current APY in percentage points.",
            "example": 5.21
          }
        }
      },
      "PortfolioSlice": {
        "type": "object",
        "description": "One position in the proposed allocation.",
        "properties": {
          "vaultId": {
            "type": "string",
            "description": "Vault identifier (same as vaultId in /scores).",
            "example": "morpho:1:0xbeefc1cdafc5b4a649b54d07afc6bf0f75c6f4e2"
          },
          "protocol": {
            "type": "string",
            "example": "morpho"
          },
          "chainId": {
            "type": "integer",
            "example": 1
          },
          "asset": {
            "type": "string",
            "description": "Underlying asset symbol.",
            "example": "USDC"
          },
          "name": {
            "type": "string",
            "description": "Vault display name.",
            "example": "STEAKUSDTBETHENA"
          },
          "apy": {
            "type": "number",
            "description": "Current APY in percentage points.",
            "example": 12.18706
          },
          "tvl": {
            "type": "number",
            "description": "Total value locked, USD.",
            "example": 11780173
          },
          "composite": {
            "type": "number",
            "description": "The vault's 0–100 Atlas Score at allocation time.",
            "example": 78.762
          },
          "fraction": {
            "type": "number",
            "description": "Share of principal as a fraction, rounded to 1e-4.",
            "example": 0.15
          },
          "allocationPct": {
            "type": "number",
            "description": "Share of principal as a percentage, 2 dp.",
            "example": 15
          },
          "allocationUsd": {
            "type": "number",
            "description": "Dollar amount allocated to this vault.",
            "example": 1500
          },
          "vaultAddress": {
            "type": "string",
            "description": "On-chain vault contract address.",
            "example": "0xbeefc1cdafc5b4a649b54d07afc6bf0f75c6f4e2"
          },
          "underlyingToken": {
            "type": "string",
            "description": "Address of the token the vault accepts.",
            "example": "0xc139190f447e929f090edeb554d95abb8b18ac1c"
          }
        }
      },
      "FactorResult": {
        "type": "object",
        "properties": {
          "score": {
            "type": "number",
            "description": "Sub-score 0–100. For EXPLOIT_HISTORY this is multiplier × 100.",
            "example": 82
          },
          "weight": {
            "type": "number",
            "description": "Weight inside its pillar's arithmetic mean. 0 for EXPLOIT_HISTORY (multiplicative).",
            "example": 0.3
          },
          "rawValue": {
            "type": "number",
            "description": "The raw input that drove the score (TVL USD, audit count, slope, ...).",
            "example": 5.2
          },
          "label": {
            "type": "string",
            "description": "Plain-English description of the input.",
            "example": "5.2% APY, stablecoin"
          },
          "pillar": {
            "type": "string",
            "enum": [
              "yield",
              "safety",
              "liquidity",
              "sustainability"
            ]
          }
        }
      },
      "VaultFactors": {
        "type": "object",
        "properties": {
          "vaultId": {
            "type": "string",
            "example": "aave-v3:8453:0x4e65fe4dba92790696d040ac24aa414708f5c0ab"
          },
          "chainId": {
            "type": "integer",
            "example": 8453
          },
          "protocolId": {
            "type": "string",
            "example": "aave-v3"
          },
          "composite": {
            "type": "number",
            "example": 87.5
          },
          "label": {
            "type": "string",
            "enum": [
              "Conservative",
              "Balanced",
              "Aggressive"
            ]
          },
          "pillarScores": {
            "type": "object",
            "properties": {
              "yield": {
                "type": "number"
              },
              "safety": {
                "type": "number"
              },
              "liquidity": {
                "type": "number"
              },
              "sustainability": {
                "type": "number"
              }
            }
          },
          "factors": {
            "type": "object",
            "description": "All 16 factors keyed by name: YIELD_LEVEL, APY_STABILITY, APY_PERSISTENCE, YIELD_SOURCE, AUDIT_QUALITY, PROTOCOL_MATURITY, TVL_DEPTH_SAFETY, TVL_STABILITY, ADMIN_TOPOLOGY, CHAIN_SECURITY, EXPLOIT_HISTORY, WITHDRAWAL_TYPE, TVL_DEPTH_LIQUIDITY, EMISSION_DEPENDENCY, ASSET_RISK, UTILIZATION_HEALTH.",
            "additionalProperties": {
              "$ref": "#/components/schemas/FactorResult"
            }
          },
          "dataQuality": {
            "type": "string",
            "enum": [
              "sufficient",
              "partial",
              "fallback"
            ]
          },
          "scoredAt": {
            "type": "string",
            "format": "date-time"
          },
          "scorerVersion": {
            "type": "string",
            "example": "2.2.0"
          }
        }
      },
      "VaultMetrics": {
        "type": "object",
        "description": "Tier 1 live metrics for one vault. Separate from the Atlas Score, never folded into the composite.",
        "properties": {
          "vaultId": {
            "type": "string",
            "description": "Stable identifier: protocol:chainId:address.",
            "example": "aave-v3:8453:0x4e65fe4dba92790696d040ac24aa414708f5c0ab"
          },
          "chainId": {
            "type": "integer",
            "example": 8453
          },
          "yieldQuality": {
            "type": "number",
            "nullable": true,
            "description": "0-100. How much of the headline APY comes from the vault's own economics rather than transient incentives. Null when the APY split is unavailable.",
            "example": 82
          },
          "yieldQualityReason": {
            "type": "string",
            "nullable": true,
            "description": "Why that value was assigned, or why it is null."
          },
          "exitSafety": {
            "type": "number",
            "nullable": true,
            "description": "0-100. How safely a position can be exited at size. Null when the underlying liquidity data is unavailable.",
            "example": 74
          },
          "exitSafetyReason": {
            "type": "string",
            "nullable": true,
            "description": "Why that value was assigned, or why it is null."
          },
          "scorerRunDate": {
            "type": "string",
            "format": "date",
            "description": "The day (UTC) these metrics were computed.",
            "example": "2026-09-04"
          }
        }
      },
      "VaultRoute": {
        "type": "object",
        "description": "Round-trip route-survival verdict for one vault: send USDC in, take the position, sell it straight back, on the exact path the app itself executes. \"Can I get out at size, measured today\" — not a liquidity claim, a real quote. SAME-CHAIN ONLY: funding USDC sits on the vault's own chain. Nothing here describes bridging in from another chain (see `probe`).",
        "properties": {
          "vaultId": {
            "type": "string",
            "nullable": true,
            "description": "Stable identifier: protocol:chainId:address. Null when unscreened.",
            "example": "pendle:1:0x5949c3bfdf9babd8bf011d9d8dbb1a2c3e10d8b8"
          },
          "chainId": {
            "type": "integer",
            "example": 1
          },
          "address": {
            "type": "string",
            "description": "The address that was asked for. Present only on the unscreened shape."
          },
          "tracked": {
            "type": "boolean",
            "description": "False when the vault's chain has no route probe. Route tracking covers EVM chains only, so a Solana vault is not tracked. Not tracked is neither a pass nor a failure: there is simply no verdict.",
            "example": true
          },
          "note": {
            "type": "string",
            "description": "Present only when `tracked` is false; says why there is no verdict."
          },
          "screened": {
            "type": "boolean",
            "description": "False when this vault has never been screened — no verdict is fabricated.",
            "example": true
          },
          "verdict": {
            "type": "string",
            "nullable": true,
            "enum": [
              "OK",
              "DEGRADED",
              "DANGEROUS",
              "UNPRICEABLE",
              "NO_ROUTE",
              "SKIPPED",
              null
            ],
            "description": "OK/DEGRADED are tradable. DANGEROUS, UNPRICEABLE and NO_ROUTE block deposits in the live engine. SKIPPED means our own probe was rate-limited, not a statement about the vault. Null when unscreened.",
            "example": "OK"
          },
          "reasonClass": {
            "type": "string",
            "nullable": true,
            "enum": [
              "protocol_refused",
              "no_aggregator_quote",
              "dust",
              "no_amount",
              "rate_limited",
              "unmapped",
              null
            ],
            "description": "Why a non-OK verdict was reached. `protocol_refused`: the protocol's own router (Pendle Convert) refuses the position — the vault is dead for entry or exit. `no_aggregator_quote`: no same-chain aggregator quotes USDC into the underlying (typical for LP-token vaults); the vault may still be exitable by redeeming and unwinding by hand. `dust`: value came back but below the 85% floor. Null for OK/DEGRADED.",
            "example": "protocol_refused"
          },
          "usdIn": {
            "type": "number",
            "nullable": true,
            "description": "USDC sent on the worst probed notional.",
            "example": 5000
          },
          "usdOut": {
            "type": "number",
            "nullable": true,
            "description": "USDC that came back after entering and exiting the position.",
            "example": 4991
          },
          "retained": {
            "type": "number",
            "nullable": true,
            "description": "usdOut / usdIn on the worst probed notional.",
            "example": 0.9982
          },
          "path": {
            "type": "string",
            "nullable": true,
            "enum": [
              "pendle",
              "composer",
              "native",
              null
            ],
            "description": "Which execution path was probed — the same one the app itself calls.",
            "example": "pendle"
          },
          "probe": {
            "type": "object",
            "description": "What was measured, stated on every response. `scope` is always `same-chain`; `crossChain` is always `not probed`.",
            "properties": {
              "scope": {
                "type": "string",
                "example": "same-chain"
              },
              "funding": {
                "type": "string"
              },
              "legs": {
                "type": "string"
              },
              "notionalsUsd": {
                "type": "array",
                "items": {
                  "type": "number"
                },
                "example": [
                  100,
                  5000
                ]
              },
              "router": {
                "type": "object",
                "additionalProperties": {
                  "type": "string"
                }
              },
              "crossChain": {
                "type": "string",
                "example": "not probed"
              }
            }
          },
          "checkedAt": {
            "type": "string",
            "format": "date-time",
            "nullable": true,
            "description": "When this verdict was last measured. Null when unscreened."
          },
          "cost": {
            "$ref": "#/components/schemas/RouteCost"
          }
        }
      },
      "RouteCost": {
        "type": "object",
        "nullable": true,
        "description": "Estimated round-trip execution cost at `notionalUsd`, same-chain, on the named router. fixedUsd = (vault entry + exit + swap + router approvals) gas × USD per gas; allInBps = fixedUsd / notional + proportional (router fee + price impact). An ESTIMATE at the stated gas basis: realised cost varies with gas at execution time. Null when the vault never completed a round trip. Unmeasured inputs are listed in `missing`; where a component is known to be missing, the figure is marked `floor: true` (a lower bound).",
        "properties": {
          "notionalUsd": {
            "type": "number",
            "example": 10000
          },
          "router": {
            "type": "string",
            "enum": [
              "lifi",
              "pendle-convert",
              "none"
            ],
            "description": "`none` = same token in and out, nothing swapped. Costs differ by router: the Atlas app deposits via Relay."
          },
          "gasBasis": {
            "type": "string",
            "enum": [
              "p50_7d"
            ],
            "description": "Headline basis. Trailing 7-day median",
            "so a single spike cannot set it.": null
          },
          "fixedUsd": {
            "type": "object",
            "properties": {
              "current": {
                "type": "number",
                "nullable": true
              },
              "p50_7d": {
                "type": "number",
                "nullable": true
              },
              "p90_7d": {
                "type": "number",
                "nullable": true
              },
              "floor": {
                "type": "boolean",
                "description": "True when a component is known to be missing (L1 data fee on L2s (Base, Optimism, Arbitrum), or router gas not reported): the true figure is higher."
              }
            }
          },
          "gasUnits": {
            "type": "object",
            "properties": {
              "swap": {
                "type": "integer",
                "nullable": true
              },
              "vaultEntry": {
                "type": "integer"
              },
              "vaultExit": {
                "type": "integer"
              },
              "approvals": {
                "type": "integer",
                "description": "USDC->router on entry + underlying->router on exit. LiFi only; 0 for Pendle Convert or a same-token round trip."
              },
              "total": {
                "type": "integer"
              },
              "vaultGasSource": {
                "type": "string",
                "enum": [
                  "observed",
                  "assumed"
                ],
                "description": "`observed` = median of real transactions; `assumed` = no transaction exists yet."
              },
              "highVariance": {
                "type": "boolean",
                "description": "True when the protocol's vault-action gas is a median of observed exits that disagreed by more than 2x."
              }
            }
          },
          "proportionalBps": {
            "type": "object",
            "properties": {
              "value": {
                "type": "number",
                "nullable": true
              },
              "routerFeeBps": {
                "type": "number",
                "nullable": true
              },
              "priceImpactBps": {
                "type": "number",
                "nullable": true
              },
              "extrapolated": {
                "type": "boolean",
                "description": "True outside the probed $100–$5,000: the nearest probe is held flat, not fitted."
              }
            }
          },
          "allInBps": {
            "type": "object",
            "properties": {
              "current": {
                "type": "number",
                "nullable": true
              },
              "p50_7d": {
                "type": "number",
                "nullable": true
              },
              "p90_7d": {
                "type": "number",
                "nullable": true
              },
              "floor": {
                "type": "boolean",
                "description": "Same as fixedUsd.floor. True means these figures are a LOWER BOUND; treat as not passed in any gate."
              }
            }
          },
          "missing": {
            "type": "array",
            "items": {
              "type": "string",
              "enum": [
                "swap_gas",
                "l1_data_fee",
                "gas_current",
                "gas_history",
                "no_route_at_size",
                "gas_history_partial",
                "high_variance_gas"
              ]
            }
          }
        }
      },
      "GasRegime": {
        "type": "object",
        "properties": {
          "chainId": {
            "type": "integer",
            "example": 1
          },
          "current": {
            "type": "object",
            "nullable": true,
            "description": "Newest 15-minute sample; null when older than 1 hour.",
            "properties": {
              "gwei": {
                "type": "number"
              },
              "nativeUsd": {
                "type": "number"
              },
              "usdPerMillionGas": {
                "type": "number"
              },
              "at": {
                "type": "string",
                "format": "date-time"
              }
            }
          },
          "usdPerMillionGas": {
            "type": "object",
            "description": "Trailing 7-day percentiles; null until 6 hours of samples exist.",
            "properties": {
              "p10_7d": {
                "type": "number",
                "nullable": true
              },
              "p50_7d": {
                "type": "number",
                "nullable": true
              },
              "p90_7d": {
                "type": "number",
                "nullable": true
              }
            }
          },
          "samples": {
            "type": "integer"
          },
          "coverageHours": {
            "type": "number"
          }
        }
      }
    },
    "responses": {
      "ValidationError": {
        "description": "A query parameter is missing or out of range. The body carries per-field detail under `details.fieldErrors`.",
        "content": {
          "application/json": {
            "schema": {
              "type": "object",
              "properties": {
                "success": {
                  "type": "boolean",
                  "example": false
                },
                "error": {
                  "type": "string",
                  "example": "Validation error"
                },
                "details": {
                  "type": "object",
                  "description": "Zod field errors, keyed by parameter name."
                }
              }
            }
          }
        }
      }
    }
  },
  "paths": {
    "/scores": {
      "get": {
        "operationId": "getScores",
        "summary": "Latest scores",
        "description": "The most recent composite + pillar scores for every tracked vault — one row per vault, newest first.",
        "parameters": [
          {
            "name": "chainId",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer"
            },
            "description": "Filter to one EVM chain id.",
            "example": 8453
          },
          {
            "name": "protocolId",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            },
            "description": "Filter to one protocol slug.",
            "example": "morpho"
          },
          {
            "name": "address",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            },
            "description": "One vault by on-chain address (last segment of vaultId, case-insensitive). Returns at most one row per chain.",
            "example": "0xbeeF010f9cb27031ad51e3333f9aF9C6B1228183"
          },
          {
            "name": "asset",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            },
            "description": "Underlying asset symbol, case-insensitive exact match. Accepts a comma list of up to 10 (USDC,USDT0). Exact means exact: USDC does not match syrupUSDC. Use explain=true to see near-miss symbols.",
            "example": "USDC"
          },
          {
            "name": "minScore",
            "in": "query",
            "required": false,
            "schema": {
              "type": "number",
              "minimum": 0,
              "maximum": 100
            },
            "description": "Keep vaults with composite >= this value."
          },
          {
            "name": "minTvl",
            "in": "query",
            "required": false,
            "schema": {
              "type": "number",
              "minimum": 0
            },
            "description": "Keep vaults whose LIVE TVL (USD) is >= this value."
          },
          {
            "name": "minApy",
            "in": "query",
            "required": false,
            "schema": {
              "type": "number",
              "minimum": 0
            },
            "description": "Keep vaults whose LIVE APY (percentage points) is >= this value. A vault with no APY never clears it."
          },
          {
            "name": "routable",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "true"
              ]
            },
            "description": "Only vaults a deposit can reach. Excludes vaults flagged unroutable and, failing closed, vaults missing from the live catalog. Only the value true is accepted."
          },
          {
            "name": "excludeFallback",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "true"
              ]
            },
            "description": "Exclude rows scored on fallback data (dataQuality = fallback)."
          },
          {
            "name": "explain",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "true"
              ]
            },
            "description": "Adds an explain block saying what the filters removed: counts per filter, the dropped vaults and why (best-ranked first, with up to 2 slots held for vaults dropped as unreachable or low-confidence), and near-miss asset symbols. Counts are per filter, so a vault failing two filters is counted under both."
          },
          {
            "name": "sort",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "composite",
                "apy",
                "tvl"
              ]
            },
            "description": "Sort descending by this field (apy and tvl are the live values). Omit to keep the default order."
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 200
            },
            "description": "Return at most this many rows, applied after filtering and sorting."
          },
          {
            "name": "fields",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "compact"
              ]
            },
            "description": "compact returns the small CompactScore row instead of the full Score."
          }
        ],
        "responses": {
          "200": {
            "description": "Latest scores for the tracked vaults that match.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "success": {
                      "type": "boolean",
                      "example": true
                    },
                    "scorerVersion": {
                      "type": "string",
                      "example": "2.2.0"
                    },
                    "count": {
                      "type": "integer",
                      "example": 570,
                      "description": "Rows returned."
                    },
                    "total": {
                      "type": "integer",
                      "example": 570,
                      "description": "Rows matching the filters before limit is applied."
                    },
                    "applied": {
                      "type": "object",
                      "description": "Every filter this server honoured, echoed back. If you sent a parameter and it is missing here, the API predates it and the rows are not filtered by it.",
                      "additionalProperties": true
                    },
                    "ignoredParams": {
                      "type": "array",
                      "description": "Query keys this endpoint does not recognise, sorted, at most 20 of at most 64 characters. Present only when there is at least one. An unknown key is not an error: it is ignored, so a typo such as minAPY returns 200 with the rows NOT filtered by it. If you expected a filter to apply, check that its name is not listed here.",
                      "items": {
                        "type": "string"
                      }
                    },
                    "explain": {
                      "type": "object",
                      "description": "Only with explain=true.",
                      "properties": {
                        "universe": {
                          "type": "integer",
                          "description": "Deduped vaults scored."
                        },
                        "candidates": {
                          "type": "integer",
                          "description": "Vaults passing chainId, protocolId, address and asset."
                        },
                        "matched": {
                          "type": "integer"
                        },
                        "returned": {
                          "type": "integer"
                        },
                        "truncated": {
                          "type": "boolean",
                          "description": "More matched than limit returned."
                        },
                        "failed": {
                          "type": "object",
                          "description": "Candidates failing each requested filter. Not exclusive.",
                          "properties": {
                            "belowMinScore": {
                              "type": "integer"
                            },
                            "belowMinTvl": {
                              "type": "integer"
                            },
                            "belowMinApy": {
                              "type": "integer"
                            },
                            "noDepositRoute": {
                              "type": "integer",
                              "description": "Route screen says a deposit cannot be exited."
                            },
                            "noLiveEntry": {
                              "type": "integer",
                              "description": "Not in the live catalog (fails closed). A high count here with a low liveCatalogEntries means a cold cache, not blocked routes."
                            },
                            "fallbackData": {
                              "type": "integer"
                            }
                          }
                        },
                        "liveCatalogEntries": {
                          "type": "integer"
                        },
                        "notable": {
                          "type": "array",
                          "description": "Up to 5 dropped vaults with the reasons, ordered by the active sort. Up to 2 slots are reserved for vaults dropped for a safety reason (noDepositRoute, noLiveEntry, fallbackData) so they cannot be crowded out by higher-ranked misses; the rest are the best-ranked drops.",
                          "items": {
                            "type": "object",
                            "properties": {
                              "vaultId": {
                                "type": "string"
                              },
                              "name": {
                                "type": [
                                  "string",
                                  "null"
                                ]
                              },
                              "composite": {
                                "type": "number"
                              },
                              "apy": {
                                "type": [
                                  "number",
                                  "null"
                                ]
                              },
                              "tvl": {
                                "type": [
                                  "number",
                                  "null"
                                ]
                              },
                              "reasons": {
                                "type": "array",
                                "items": {
                                  "type": "string"
                                }
                              }
                            }
                          }
                        },
                        "similarAssets": {
                          "type": "array",
                          "description": "Symbols containing a queried symbol that the exact match left out.",
                          "items": {
                            "type": "object",
                            "properties": {
                              "asset": {
                                "type": "string"
                              },
                              "count": {
                                "type": "integer"
                              }
                            }
                          }
                        }
                      }
                    },
                    "disclaimer": {
                      "type": "string",
                      "example": "Research and information, not investment advice."
                    },
                    "data": {
                      "type": "array",
                      "items": {
                        "oneOf": [
                          {
                            "$ref": "#/components/schemas/Score"
                          },
                          {
                            "$ref": "#/components/schemas/CompactScore"
                          }
                        ]
                      }
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/catalog": {
      "get": {
        "operationId": "getCatalog",
        "summary": "Vault catalog",
        "description": "The tracked vault universe — every vault Atlas scores, with its live APY and TVL. Use it to discover vaultIds before querying /scores.",
        "parameters": [
          {
            "name": "chainId",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer"
            },
            "description": "Filter to one EVM chain id.",
            "example": 8453
          },
          {
            "name": "protocolId",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            },
            "description": "Filter to one protocol slug.",
            "example": "morpho"
          }
        ],
        "responses": {
          "200": {
            "description": "The tracked vault universe.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "success": {
                      "type": "boolean",
                      "example": true
                    },
                    "count": {
                      "type": "integer",
                      "example": 1076
                    },
                    "disclaimer": {
                      "type": "string",
                      "example": "Research and information, not investment advice."
                    },
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/CatalogEntry"
                      }
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/portfolio": {
      "get": {
        "operationId": "getPortfolio",
        "summary": "Build a portfolio",
        "description": "The Atlas Engine as a service. Give it a risk tier, a timeline, and a principal; it runs the mean-variance allocator over the current scored universe and returns the resulting allocation, sized in dollars.\n\nUnlike /scores and /catalog, this endpoint **fails closed on stale scoring**: an allocation is an active recommendation, so if the most recent scoring run is too old the endpoint returns 503 rather than allocating on stale data.\n\nThe allocator is deterministic — the same inputs against the same scoring snapshot return the same portfolio.",
        "parameters": [
          {
            "name": "riskTier",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string",
              "enum": [
                "conservative",
                "balanced",
                "aggressive"
              ]
            },
            "description": "Which tier policy to allocate under. See the tier policy page.",
            "example": "balanced"
          },
          {
            "name": "principal",
            "in": "query",
            "required": true,
            "schema": {
              "type": "number",
              "minimum": 100,
              "maximum": 1000000
            },
            "description": "Amount to allocate, USD. Must be between 100 and 1,000,000.",
            "example": 10000
          },
          {
            "name": "timeline",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "short",
                "medium",
                "long"
              ],
              "default": "medium"
            },
            "description": "Investment horizon. Nudges risk appetite within the tier's band — short is more variance-averse, long more return-seeking — and a short horizon also tightens the cap on vaults with little history.",
            "example": "medium"
          }
        ],
        "responses": {
          "200": {
            "description": "The proposed allocation.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "success": {
                      "type": "boolean",
                      "example": true
                    },
                    "riskTier": {
                      "type": "string",
                      "example": "balanced"
                    },
                    "timeline": {
                      "type": "string",
                      "example": "medium"
                    },
                    "principal": {
                      "type": "number",
                      "example": 10000
                    },
                    "strategy": {
                      "type": "string",
                      "enum": [
                        "MVO",
                        "naive",
                        "none"
                      ],
                      "description": "Which allocator produced the result. MVO is the mean-variance optimiser; naive is the equal-weight fallback; none means no allocation could be produced (see `note`).",
                      "example": "MVO"
                    },
                    "fallback": {
                      "type": "boolean",
                      "description": "True when the result came from the fallback path rather than MVO.",
                      "example": false
                    },
                    "allocations": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/PortfolioSlice"
                      }
                    },
                    "expectedApy": {
                      "type": "number",
                      "nullable": true,
                      "description": "Principal-weighted expected APY of the allocation, percentage points.",
                      "example": 4.69
                    },
                    "expectedSharpe": {
                      "type": "number",
                      "description": "Expected Sharpe-like ratio of the allocation.",
                      "example": 14.58
                    },
                    "unallocatedUsd": {
                      "type": "number",
                      "description": "Principal left in cash because policy caps could not be satisfied. Surfaced honestly rather than force-fitted into a vault.",
                      "example": 0
                    },
                    "scorerVersion": {
                      "type": "string",
                      "example": "2.2.0"
                    },
                    "generatedAt": {
                      "type": "string",
                      "format": "date-time",
                      "description": "ISO-8601 UTC timestamp the allocation was produced."
                    },
                    "disclaimer": {
                      "type": "string",
                      "example": "Research and information, not investment advice."
                    },
                    "note": {
                      "type": "string",
                      "description": "Present only when the allocator could not fully allocate; explains why."
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/ValidationError"
          },
          "503": {
            "description": "Scoring data is stale, so no allocation was produced. Carries a `Retry-After` header (seconds).",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "success": {
                      "type": "boolean",
                      "example": false
                    },
                    "error": {
                      "type": "string",
                      "example": "Scoring data is stale — portfolio unavailable"
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/basket": {
      "post": {
        "operationId": "scoreBasket",
        "summary": "Score a basket",
        "description": "Score a basket you define. Send up to 50 positions (chainId + vault address, optional weight) and get back each position's current Atlas Score plus a weighted basket score: composite, pillars, and APY, weighted by position size. Weights are normalised, so percentages, USD amounts, and fractions all work; omit them for equal weight.\n\n**Coverage is disclosed, never assumed.** A position Atlas does not score is returned with `scored: false` and excluded from the aggregate, and `basket.scoredWeight` reports the share of the basket the score actually covers. It is never averaged in as zero.\n\nA pure computation over the request body: nothing is stored.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "positions"
                ],
                "properties": {
                  "positions": {
                    "type": "array",
                    "minItems": 1,
                    "maxItems": 50,
                    "items": {
                      "type": "object",
                      "required": [
                        "chainId",
                        "address"
                      ],
                      "properties": {
                        "chainId": {
                          "type": "integer",
                          "example": 1
                        },
                        "address": {
                          "type": "string",
                          "description": "Vault contract address, case-insensitive.",
                          "example": "0xd8a9f8b3c1b52e8a4f1e2f6c4e0b1a9c8d7e6f5a"
                        },
                        "weight": {
                          "type": "number",
                          "exclusiveMinimum": 0,
                          "description": "Position size in any unit; normalised across the basket. Omit on every position for equal weight.",
                          "example": 60
                        }
                      }
                    }
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The basket score and each position.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "success": {
                      "type": "boolean",
                      "example": true
                    },
                    "disclaimer": {
                      "type": "string",
                      "example": "Research and information, not investment advice."
                    },
                    "scorerVersion": {
                      "type": "string",
                      "example": "2.2.0"
                    },
                    "generatedAt": {
                      "type": "string",
                      "format": "date-time"
                    },
                    "basket": {
                      "type": "object",
                      "properties": {
                        "composite": {
                          "type": "number",
                          "nullable": true,
                          "description": "Weight-weighted mean of the scored positions' composites. Null when nothing in the basket is scored.",
                          "example": 74.2
                        },
                        "label": {
                          "type": "string",
                          "nullable": true,
                          "example": "Good"
                        },
                        "pillarScores": {
                          "type": "object",
                          "properties": {
                            "yield": {
                              "type": "number",
                              "nullable": true
                            },
                            "safety": {
                              "type": "number",
                              "nullable": true
                            },
                            "liquidity": {
                              "type": "number",
                              "nullable": true
                            },
                            "sustainability": {
                              "type": "number",
                              "nullable": true
                            }
                          }
                        },
                        "apy": {
                          "type": "number",
                          "nullable": true,
                          "description": "Weight-weighted live APY of the scored positions, percentage points."
                        },
                        "positions": {
                          "type": "integer",
                          "description": "Positions sent."
                        },
                        "scoredPositions": {
                          "type": "integer",
                          "description": "Positions Atlas scores."
                        },
                        "scoredWeight": {
                          "type": "number",
                          "description": "Share of basket weight covered by the aggregate, 0 to 1. Below 1 means the basket holds something Atlas does not score.",
                          "example": 1
                        },
                        "maxWeight": {
                          "type": "number",
                          "description": "Largest single position weight",
                          "0 to 1.": null
                        },
                        "chainShare": {
                          "type": "object",
                          "additionalProperties": {
                            "type": "number"
                          },
                          "description": "Basket weight by chainId."
                        },
                        "protocolShare": {
                          "type": "object",
                          "additionalProperties": {
                            "type": "number"
                          },
                          "description": "Basket weight by protocolId; unscored positions appear under `unscored`."
                        }
                      }
                    },
                    "positions": {
                      "type": "array",
                      "description": "One entry per position sent, in order. Scored entries carry the same fields as a /scores row plus `weight`.",
                      "items": {
                        "type": "object",
                        "properties": {
                          "chainId": {
                            "type": "integer"
                          },
                          "address": {
                            "type": "string"
                          },
                          "weight": {
                            "type": "number",
                            "description": "Normalised weight",
                            "0 to 1.": null
                          },
                          "scored": {
                            "type": "boolean"
                          },
                          "vaultId": {
                            "type": "string"
                          },
                          "name": {
                            "type": "string",
                            "nullable": true
                          },
                          "protocolId": {
                            "type": "string"
                          },
                          "composite": {
                            "type": "number"
                          },
                          "label": {
                            "type": "string"
                          },
                          "apy": {
                            "type": "number",
                            "nullable": true
                          },
                          "tvl": {
                            "type": "number",
                            "nullable": true
                          },
                          "dataQuality": {
                            "type": "string",
                            "enum": [
                              "sufficient",
                              "partial",
                              "fallback"
                            ]
                          },
                          "scoredAt": {
                            "type": "string",
                            "format": "date-time"
                          }
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/ValidationError"
          }
        }
      }
    },
    "/vaults/{chainId}/{address}/factors": {
      "get": {
        "operationId": "getVaultFactors",
        "summary": "16-factor breakdown for one vault",
        "description": "The \"why\" behind a composite: every one of the 16 factors (15 additive across four pillars plus the multiplicative exploit-history modifier) with its sub-score, weight, raw input and a plain-English label. Served from the stored score row, so it is exactly what the engine computed at `scoredAt`, not a recomputation.",
        "parameters": [
          {
            "name": "chainId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            },
            "example": 8453
          },
          {
            "name": "address",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "Vault contract address. Case-insensitive.",
            "example": "0x4e65fe4dba92790696d040ac24aa414708f5c0ab"
          }
        ],
        "responses": {
          "200": {
            "description": "The latest score row's factor breakdown.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "success": {
                      "type": "boolean",
                      "example": true
                    },
                    "disclaimer": {
                      "type": "string",
                      "example": "Research and information, not investment advice."
                    },
                    "data": {
                      "$ref": "#/components/schemas/VaultFactors"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/ValidationError"
          },
          "404": {
            "description": "The vault is not in the scored universe.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "success": {
                      "type": "boolean",
                      "example": false
                    },
                    "error": {
                      "type": "string",
                      "example": "No score found for this vault"
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/vaults/{chainId}/{address}/metrics": {
      "get": {
        "operationId": "getVaultMetrics",
        "summary": "Live risk metrics for one vault",
        "description": "Tier 1 live metrics for a single vault: yield quality and exit safety, each with the reason it was assigned. These are **separate from the Atlas Score** and are never folded into it — the composite answers \"how good is this vault\", these two answer \"is the yield real\" and \"can I get out\".\n\nBoth values are nullable and are never fabricated: when the underlying inputs are missing, the value is `null` and the reason says why. Metrics are recomputed on a ~24h cadence, so `scorerRunDate` is the day the values were produced.",
        "parameters": [
          {
            "name": "chainId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            },
            "description": "EVM chain id the vault lives on.",
            "example": 8453
          },
          {
            "name": "address",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "Vault contract address. Case-insensitive.",
            "example": "0x4e65fe4dba92790696d040ac24aa414708f5c0ab"
          }
        ],
        "responses": {
          "200": {
            "description": "The most recent metrics row for the vault.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "success": {
                      "type": "boolean",
                      "example": true
                    },
                    "disclaimer": {
                      "type": "string",
                      "example": "Research and information, not investment advice."
                    },
                    "data": {
                      "$ref": "#/components/schemas/VaultMetrics"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/ValidationError"
          },
          "404": {
            "description": "No metrics have been computed for this vault.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "success": {
                      "type": "boolean",
                      "example": false
                    },
                    "error": {
                      "type": "string",
                      "example": "Risk metrics not found for this vault"
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/vaults/{chainId}/{address}/route": {
      "get": {
        "operationId": "getVaultRoute",
        "summary": "Route-survival verdict for one vault",
        "description": "The wedge, not the score. An agent about to move real money needs \"can I get out at size, measured today\" more than \"is this good\". This is the same verdict `refreshAllRouteScreens` writes daily and the live engine uses to gate deposits — exposed read-only. Round trip, on the protocol's own execution path, so no pricing oracle is involved: USDC compared against USDC.\n\nA vault that has never been screened returns `screened: false` with every measurement field `null` — never a fabricated verdict. Route tracking covers EVM chains only: a Solana vault returns `tracked: false` (not tracked, which is neither a pass nor a failure).",
        "parameters": [
          {
            "name": "chainId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            },
            "description": "EVM chain id the vault lives on.",
            "example": 1
          },
          {
            "name": "address",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "Vault contract address. Case-insensitive.",
            "example": "0x5949c3bfdf9babd8bf011d9d8dbb1a2c3e10d8b8"
          },
          {
            "name": "notional",
            "in": "query",
            "required": false,
            "description": "USD size to cost the round trip at. 10 to 10,000,000, default 5,000.",
            "schema": {
              "type": "number",
              "minimum": 10,
              "maximum": 10000000,
              "default": 5000
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The most recent route-survival verdict for the vault, or the unscreened shape.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "success": {
                      "type": "boolean",
                      "example": true
                    },
                    "disclaimer": {
                      "type": "string",
                      "example": "Research and information, not investment advice."
                    },
                    "data": {
                      "$ref": "#/components/schemas/VaultRoute"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/ValidationError"
          }
        }
      }
    },
    "/gas": {
      "get": {
        "operationId": "getGasRegime",
        "summary": "Per-chain gas regime (current + trailing 7-day percentiles)",
        "responses": {
          "200": {
            "description": "One entry per chain (1, 8453, 42161, 10, 56).",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "success": {
                      "type": "boolean"
                    },
                    "disclaimer": {
                      "type": "string"
                    },
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/GasRegime"
                      }
                    }
                  }
                }
              }
            }
          }
        }
      }
    }
  }
}
