{
  "openapi": "3.0.3",
  "info": {
    "title": "RiskModels API",
    "version": "3.0.0-agent",
    "description": "POST /decompose returns variance shares and ETF hedge ratios for any US equity, separating factor exposure from the residual bet so you can hedge what you don't want. Daily history to 2000 across ~3,000 US equities.\nPrecision equity risk intelligence platform with AI agent integration.\n\n**New in v3.0.0-agent:**\n- OAuth2 client credentials flow for machine-to-machine auth\n- Plaid Investments integration for live portfolio sync\n- MCP (Model Context Protocol) server for AI agent tools\n- Compliance manifests for AI marketplace integration\n- Per-key rate limiting and scoped access control\n\n**Data**: Universe `uni_mc_3000` (top ~3,000 US stocks by market cap), date range 2000 to present (deep panel; stock-specific skill series from ~2001 after warmup), updated daily end-of-day. Model outputs are computed by the ERM3 regression system (Huber/Ridge), with Zarr v2 archives on Google Cloud Storage for research pipelines. The live API reads primarily from Supabase **V3** tables: long-form `security_history` (returns, volatility, L1/L2/L3 hedge ratios and explained-risk metrics), wide latest rows in `security_history_latest`, identity in `symbols`, plus supporting surfaces such as `trading_calendar`, `erm3_landing_chart_cache`, and `macro_factors`. Cross-sectional **rankings** (`GET /rankings/*`) are served from precomputed rank metric keys in `security_history`, not a separate `erm3_rankings` table. Hedge ratios (`*_hr`) are **dollar_ratio** ETF notionals per $1 stock and are distinct from regression **betas** (`l1_mkt_beta`, `l2_sec_beta`, `l3_sub_beta`), which are dimensionless coefficients from the hierarchical L1/L2/L3 regression. Both are exposed under `MetricsV3` and on `security_history_latest`. See `SEMANTIC_ALIASES.md` for the full HR-vs-beta convention.\n\n**Authentication Methods:**\n1. Session cookies (web app)\n2. API Key/Secret via Bearer token (external integrations)\n3. OAuth2 client credentials (AI agents, npm package)\n\n**Security Features:**\n- GCP KMS envelope encryption for portfolio data\n- Cryptographic shredding on key deletion\n- HMAC-SHA256 webhook verification\n- Row-level security (RLS) via Supabase\n\n**Pricing**: Per-request billing with prepaid balance. Cached responses are free. Many metered responses include `_agent.cost_usd` in the JSON body; others bill silently — always check the `X-API-Cost-USD` response header when present. Metered operations also expose `x-pricing` (OpenAPI extension) with `capability_id`, `tier` (`baseline` | `premium`), `model`, `cost_usd`, `billing_code`, and optional `min_charge` / per-token fields — aligned with `GET /pricing` and `lib/agent/capabilities.ts`.\n**Lineage**: JSON data responses include `_metadata` (model version, data_as_of, factor set) and headers `X-Risk-Model-Version`, `X-Data-As-Of`, `X-Factor-Set-Id`, `X-Universe-Size`. Parquet and CSV bodies omit `_metadata`; use the same `X-Risk-*` headers on the HTTP response.\n**Tabular exports (Parquet / CSV):** For bulk historical loads, time-series endpoints accept `format=parquet` or `format=csv`, or (when `format` is omitted) `Accept: application/vnd.apache.parquet` or `Accept: text/csv`. Successful responses use `Content-Type: application/vnd.apache.parquet` or `text/csv; charset=utf-8` and `Content-Disposition: attachment` with a descriptive filename. Each export is a single flat table; column names and types match the per-operation schemas referenced in the response media type descriptions (aligned with JSON row objects or zipped parallel arrays). Parquet is produced via schema inference from the first row (UTF8 strings, DOUBLE floats, INT64 integers, BOOLEAN, TIMESTAMP_MILLIS for JavaScript Date values). CSV uses a header row, comma separators, UTF-8, and RFC 4180-style quoting when needed. Prefer Parquet for typed columnar stacks (pandas, Polars, Arrow); use CSV for spreadsheets and lightweight scripting.\n",
    "contact": {
      "name": "RiskModels Support",
      "email": "service@riskmodels.app",
      "url": "https://riskmodels.app"
    },
    "termsOfService": "https://riskmodels.app/legal",
    "license": {
      "name": "Commercial",
      "url": "https://riskmodels.app/legal"
    }
  },
  "servers": [
    {
      "url": "https://riskmodels.app/api",
      "description": "Production — all metered endpoints (`/metrics`, `/snapshot`, `/decompose`, `/batch/analyze`, `/portfolio/*`, etc.)."
    },
    {
      "url": "https://riskmodels.app",
      "description": "Discovery origin — `/.well-known/*` manifests (`mcp.json`, `ai-plugin.json`, `agentic-disclosure.json`) live at the site origin without the `/api` prefix. Generated clients that hard-code the first server entry must strip `/api` before requesting these paths."
    }
  ],
  "components": {
    "securitySchemes": {
      "BearerAuth": {
        "type": "http",
        "scheme": "bearer",
        "bearerFormat": "rm_agent_{environment}_{random}_{checksum}",
        "description": "API key in format `rm_agent_{live|test}_{random}_{checksum}` or `rm_user_{random}_{checksum}`. Obtain via `POST /api/auth/provision` or from riskmodels.app/settings → API Keys. Include as: `Authorization: Bearer rm_agent_live_...`\n"
      },
      "OAuth2AuthorizationCode": {
        "type": "oauth2",
        "description": "OAuth 2.0 authorization code flow with PKCE (S256), used by MCP clients (Claude Desktop, Cursor, ChatGPT Developer Mode, Grok) that self-register via `POST /api/oauth/register`. Public clients only — no `client_secret` is issued and `token_endpoint_auth_method` is `none`.\n\nThe access token this flow returns **is** an `rm_user_*` API key (1 h TTL, 30 d rotating refresh token), so it authenticates every `BearerAuth` operation below, not just `/api/mcp/sse`. Scope is currently informational: the API records the presented scope for telemetry but authorises by key validity and account balance, not by scope.\n\nDiscovery metadata: `/.well-known/oauth-authorization-server`.\n",
        "flows": {
          "authorizationCode": {
            "authorizationUrl": "https://riskmodels.app/oauth/authorize",
            "tokenUrl": "https://riskmodels.app/api/oauth/token",
            "refreshUrl": "https://riskmodels.app/api/oauth/token",
            "scopes": {
              "mcp:read": "Read access to risk analytics (the only scope the authorization server advertises)"
            }
          }
        }
      }
    },
    "parameters": {
      "FormatQueryTabular": {
        "name": "format",
        "in": "query",
        "required": false,
        "schema": {
          "type": "string",
          "enum": [
            "json",
            "parquet",
            "csv"
          ],
          "default": "json"
        },
        "description": "Response carrier. `json` (default): structured payload for this operation (body includes `_metadata` where documented). `parquet` / `csv`: a single flat table of time-series rows only — lineage comes from `X-Risk-Model-Version`, `X-Data-As-Of`, `X-Factor-Set-Id`, `X-Universe-Size`. When this parameter is omitted, `Accept: application/vnd.apache.parquet` or `Accept: text/csv` selects the tabular format.\n"
      }
    },
    "schemas": {
      "AgentMeta": {
        "type": "object",
        "description": "Per-request billing and telemetry metadata appended to metered responses.",
        "properties": {
          "cost_usd": {
            "type": "number",
            "format": "float",
            "description": "Cost deducted from prepaid balance for this request. 0 if served from cache.",
            "example": 0.005
          },
          "latency_ms": {
            "type": "integer",
            "description": "Server processing time in milliseconds.",
            "example": 145
          },
          "request_id": {
            "type": "string",
            "description": "Unique request identifier for support lookups.",
            "example": "req_abc123xyz"
          },
          "confidence": {
            "type": "object",
            "properties": {
              "overall": {
                "type": "number",
                "format": "float",
                "minimum": 0,
                "maximum": 1,
                "example": 0.98
              },
              "factors": {
                "type": "object",
                "additionalProperties": {
                  "type": "number"
                }
              }
            }
          },
          "data_freshness": {
            "type": "string",
            "format": "date-time",
            "description": "ISO timestamp of the data's as-of datetime.",
            "example": "2026-02-21T10:30:00Z"
          },
          "billing_code": {
            "type": "string",
            "description": "Internal billing classification for this request type.",
            "example": "ticker_returns_v2"
          },
          "cache_status": {
            "type": "string",
            "enum": [
              "HIT",
              "MISS",
              "BYPASS"
            ],
            "description": "Whether this response was served from cache."
          },
          "llm_cost_usd": {
            "type": "number",
            "format": "float",
            "description": "POST /chat only: portion of cost from LLM token billing (estimated preflight charge).\n"
          },
          "tool_cost_usd": {
            "type": "number",
            "format": "float",
            "description": "POST /chat only; sum of internal data tool charges for this turn."
          },
          "tool_calls": {
            "type": "integer",
            "description": "POST /chat only; number of tool invocations in this request."
          }
        }
      },
      "ChatToolCallSummary": {
        "type": "object",
        "description": "One internal tool invocation from POST /chat (billing + latency).",
        "properties": {
          "tool": {
            "type": "string",
            "description": "OpenAI function name (e.g. get_risk_metrics)."
          },
          "capability": {
            "type": "string",
            "nullable": true,
            "description": "Billing capability id, or null for free tools (e.g. search_tickers)."
          },
          "cost_usd": {
            "type": "number",
            "format": "float"
          },
          "latency_ms": {
            "type": "integer"
          },
          "error": {
            "type": "string",
            "nullable": true
          }
        }
      },
      "RiskMetadata": {
        "type": "object",
        "description": "Lineage and provenance metadata for all data responses. Included in response body as _metadata and as X-Risk-* headers.\n",
        "properties": {
          "model_version": {
            "type": "string",
            "description": "ERM3 model version (e.g. ERM3-L3-v30).",
            "example": "ERM3-L3-v30"
          },
          "data_as_of": {
            "type": "string",
            "format": "date",
            "description": "Latest trading date reflected in the response (model outputs / latest snapshot).",
            "example": "2026-03-17"
          },
          "factor_set_id": {
            "type": "string",
            "description": "Factor set identifier.",
            "example": "SPY_uni_mc_3000"
          },
          "universe_size": {
            "type": "integer",
            "description": "Number of stocks in the universe.",
            "example": 2987
          },
          "wiki_uri": {
            "type": "string",
            "format": "uri",
            "description": "Methodology documentation URL.",
            "example": "https://riskmodels.app/docs/methodology/erm3-l3"
          },
          "factors": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "L3 factor ETF tickers (market + 11 sectors).",
            "example": [
              "SPY",
              "XLK",
              "XLF",
              "XLV",
              "XLE",
              "XLI",
              "XLY",
              "XLP",
              "XLU",
              "XLB",
              "XLRE",
              "XLC"
            ]
          },
          "data_source": {
            "type": "string",
            "enum": [
              "zarr"
            ],
            "description": "Present on history-style JSON responses. Always `\"zarr\"` as of the pure-Zarr SSOT cutover: all historical time series (daily metrics, hedge weights, returns decomposition, rankings) are served from consolidated Zarr stores on GCS. The Supabase `security_history` table has been removed; the legacy `\"supabase\"` value is retained in clients only for backwards compatibility.\n"
          },
          "range": {
            "type": "array",
            "minItems": 2,
            "maxItems": 2,
            "items": {
              "type": "string",
              "format": "date"
            },
            "description": "Optional inclusive ISO date bounds of the returned history window when `data_source` is set.\n",
            "example": [
              "2018-01-02",
              "2026-04-14"
            ]
          }
        }
      },
      "EstimateResponse": {
        "type": "object",
        "description": "Cost estimate for a request before it is made.",
        "required": [
          "estimated_cost_usd",
          "capability",
          "pricing_model",
          "note"
        ],
        "properties": {
          "estimated_cost_usd": {
            "type": "number",
            "format": "float",
            "example": 0.008
          },
          "estimated_tokens": {
            "type": "integer",
            "example": 400
          },
          "estimated_rows": {
            "type": "integer",
            "description": "Estimated row count for time-series endpoints."
          },
          "estimated_bytes": {
            "type": "integer",
            "description": "Estimated response size in bytes."
          },
          "capability": {
            "type": "string",
            "example": "ticker-returns"
          },
          "pricing_model": {
            "type": "string",
            "enum": [
              "per_request",
              "per_position",
              "per_token",
              "subscription"
            ]
          },
          "unit_cost_usd": {
            "type": "number",
            "format": "float"
          },
          "min_charge": {
            "type": "number",
            "format": "float"
          },
          "note": {
            "type": "string",
            "example": "Actual cost may vary. Cached responses are free."
          }
        }
      },
      "MetricsV3": {
        "type": "object",
        "description": "V3 metric keys as returned inside `GET /metrics/{ticker}` under the `metrics` property. Suffixes: `_mkt` market, `_sec` sector, `_sub` subsector, `_res` residual. Hedge ratios (HR) are dollars of ETF to trade per $1 of stock; explained risk (ER) are variance fractions (0–1). Keys `l1_cfr`–`l3_rr` are daily simple returns from returns decomposition:\n  - `*_cfr` (combined factor return) is CUMULATIVE through the level: `l1_cfr = l1_fr`,\n    `l2_cfr = l1_fr + l2_fr`, `l3_cfr = l1_fr + l2_fr + l3_fr`.\n  - `*_fr`  (factor return, INCREMENTAL) is each level's own contribution —\n    `l2_fr` is what the sector factor adds on top of L1 alone, `l3_fr` is what\n    the subsector factor adds on top of L1+L2. Use these for stacked-bar decomposition\n    charts or for agent-side attribution (\"what drove today's return at each level?\").\n  - `*_rr`  (residual return) is what's left after factors through that level —\n    `gross_return ≈ l3_cfr + l3_rr`.\nKeys `lstar_rr` and `lstar_level` carry the Lstar-dispatched residual return and the level that was picked, per-(teo, symbol), at the canonical 1% threshold. Use these when you want \"the best residual\" — the idiosyncratic leg at the same hedge depth as production HR/ER. For a custom threshold, hit `GET /lstar`. None of these are ER fields and none come from hedge-weight explainability. Keys `l1_mkt_beta`, `l2_sec_beta`, `l3_sub_beta` are dimensionless hierarchical regression betas (one per level: 1 for L1, 2 for L2, 3 for L3 — though only the L*-introduced new factor is stored at each level). These are NOT the same as hedge ratios — betas are regression coefficients, hedge ratios are dollar-notional ratios.\n",
        "properties": {
          "vol_23d": {
            "type": "number",
            "format": "float",
            "nullable": true,
            "description": "Annualised realised volatility (23 trading days), decimal fraction."
          },
          "vol_252d_ann": {
            "type": "number",
            "format": "float",
            "nullable": true,
            "description": "Annualised realised volatility over the trailing ~252 trading days, decimal fraction. Computed on demand from daily `returns_gross` and cached per (symbol, data_as_of); matches the horizon of 1y return cascades so callers can size positions on a common basis. Null when fewer than 20 usable daily observations are available.\n"
          },
          "price_close": {
            "type": "number",
            "format": "float",
            "nullable": true
          },
          "market_cap": {
            "type": "number",
            "format": "float",
            "nullable": true
          },
          "stock_var": {
            "type": "number",
            "format": "float",
            "nullable": true
          },
          "l1_mkt_hr": {
            "type": "number",
            "format": "float",
            "nullable": true
          },
          "l1_mkt_er": {
            "type": "number",
            "format": "float",
            "nullable": true
          },
          "l1_res_er": {
            "type": "number",
            "format": "float",
            "nullable": true
          },
          "l1_cfr": {
            "type": "number",
            "format": "float",
            "nullable": true,
            "description": "Daily simple combined factor return through L1 (market), decimal (same convention as returns_gross). From returns-decomposition zarr (ds_erm3_returns), not hedge weights. Not a variance fraction — unlike l1_mkt_er. At L1 this is equal to l1_fr (there's no level below market to add on top of).\n"
          },
          "l1_fr": {
            "type": "number",
            "format": "float",
            "nullable": true,
            "description": "Daily simple factor return for L1 (market) — the INCREMENTAL contribution of the market factor alone. At L1 this equals l1_cfr. Use `l1_fr`/`l2_fr`/`l3_fr` for stacked-bar factor decomposition charts and for per-level P&L attribution.\n"
          },
          "l1_rr": {
            "type": "number",
            "format": "float",
            "nullable": true,
            "description": "Daily simple residual return at L1 (decimal). Not l1_res_er (explained risk)."
          },
          "l2_mkt_hr": {
            "type": "number",
            "format": "float",
            "nullable": true
          },
          "l2_sec_hr": {
            "type": "number",
            "format": "float",
            "nullable": true
          },
          "l2_mkt_er": {
            "type": "number",
            "format": "float",
            "nullable": true
          },
          "l2_sec_er": {
            "type": "number",
            "format": "float",
            "nullable": true
          },
          "l2_res_er": {
            "type": "number",
            "format": "float",
            "nullable": true
          },
          "l2_cfr": {
            "type": "number",
            "format": "float",
            "nullable": true,
            "description": "Daily simple combined factor return through L2 (market + sector), decimal. Equals l1_fr + l2_fr."
          },
          "l2_fr": {
            "type": "number",
            "format": "float",
            "nullable": true,
            "description": "Daily simple factor return for L2 (sector) — the INCREMENTAL contribution the sector factor adds on top of L1 (market). For a sector-led rally this is the number to highlight in charts.\n"
          },
          "l2_rr": {
            "type": "number",
            "format": "float",
            "nullable": true,
            "description": "Daily simple residual return at L2 (decimal)."
          },
          "l3_mkt_hr": {
            "type": "number",
            "format": "float",
            "nullable": true
          },
          "l3_sec_hr": {
            "type": "number",
            "format": "float",
            "nullable": true
          },
          "l3_sub_hr": {
            "type": "number",
            "format": "float",
            "nullable": true
          },
          "l3_mkt_er": {
            "type": "number",
            "format": "float",
            "nullable": true
          },
          "l3_sec_er": {
            "type": "number",
            "format": "float",
            "nullable": true
          },
          "l3_sub_er": {
            "type": "number",
            "format": "float",
            "nullable": true
          },
          "l3_res_er": {
            "type": "number",
            "format": "float",
            "nullable": true,
            "description": "Idiosyncratic variance fraction at L3."
          },
          "l3_cfr": {
            "type": "number",
            "format": "float",
            "nullable": true,
            "description": "Daily simple combined factor return through L3 (market + sector + subsector), decimal. Equals l1_fr + l2_fr + l3_fr."
          },
          "l3_fr": {
            "type": "number",
            "format": "float",
            "nullable": true,
            "description": "Daily simple factor return for L3 (subsector) — the INCREMENTAL contribution the subsector factor adds on top of L1 + L2. Completes the three-level stacked decomposition: l1_fr + l2_fr + l3_fr + l3_rr ≈ gross_return.\n"
          },
          "l3_rr": {
            "type": "number",
            "format": "float",
            "nullable": true,
            "description": "Daily simple residual return at L3 (decimal). Distinct from l3_residual_er (ER)."
          },
          "lstar_rr": {
            "type": "number",
            "format": "float",
            "nullable": true,
            "description": "Daily simple residual return at the Lstar-dispatched level — L1→market, L2→sector, L3→subsector residual. Materialized as `residual_return.sel(level='lstar')` in `ds_erm3_returns` at the canonical 1% marginal-ER threshold. For a configurable threshold use `GET /lstar`.\n"
          },
          "lstar_level": {
            "type": "integer",
            "format": "int32",
            "nullable": true,
            "enum": [
              1,
              2,
              3,
              null
            ],
            "description": "Which cascade level Lstar dispatched to per (teo, symbol) at the canonical 1% threshold: 1 = L1 (market only), 2 = L2 (sector), 3 = L3 (subsector). `null` = no recommendation (both L2 and L3 ER are NaN — pre-IPO / delisted / masked). Maps from the uint8 `lstar_level` companion var in `ds_erm3_returns`, with 0 → null at the API boundary. For a custom threshold use `GET /lstar` which returns the level as a string.\n"
          },
          "l1_mkt_beta": {
            "type": "number",
            "format": "float",
            "nullable": true,
            "description": "Hierarchical regression beta to the symbol's L1 market factor (always SPY). Dimensionless (units of stock-return per unit of factor-return). One value per symbol from the L1 model. Sourced from `ds_erm3_betas` `factor_beta` at `fact_level=1`. NOT a hedge ratio (which is dollar-notional). NOT classical univariate beta — see `SEMANTIC_ALIASES.md` for the multivariate-vs-univariate convention.\n"
          },
          "l2_sec_beta": {
            "type": "number",
            "format": "float",
            "nullable": true,
            "description": "Hierarchical regression beta to the symbol's L2 sector factor — the symbol's specific sector ETF (e.g. XLK for AAPL, XLF for JPM). Dimensionless. From `ds_erm3_betas` `factor_beta` at `fact_level=2`. Null when the symbol has no sector classification.\n"
          },
          "l3_sub_beta": {
            "type": "number",
            "format": "float",
            "nullable": true,
            "description": "Hierarchical regression beta to the symbol's L3 subsector factor — the symbol's specific subsector ETF (e.g. RSPT for AAPL, KBE for JPM). Dimensionless. From `ds_erm3_betas` `factor_beta` at `fact_level=3`. Null when the symbol has no subsector classification.\n"
          }
        }
      },
      "LevelHedgeSnapshot": {
        "type": "object",
        "description": "One hierarchical level (L1, L2, or L3) as a standalone hedge solution — HR and ER for each populated leg plus resolved ETF mnemonics. Null legs indicate that leg is not part of this level.\n",
        "required": [
          "market_hr",
          "sector_hr",
          "subsector_hr",
          "market_er",
          "sector_er",
          "subsector_er",
          "residual_er",
          "hedge_etfs"
        ],
        "properties": {
          "market_hr": {
            "type": "number",
            "nullable": true,
            "description": "Dollar-ratio HR (ETF notional per $1 stock)."
          },
          "sector_hr": {
            "type": "number",
            "nullable": true
          },
          "subsector_hr": {
            "type": "number",
            "nullable": true
          },
          "market_er": {
            "type": "number",
            "nullable": true,
            "description": "Explained-risk variance fraction where defined for this level."
          },
          "sector_er": {
            "type": "number",
            "nullable": true
          },
          "subsector_er": {
            "type": "number",
            "nullable": true
          },
          "residual_er": {
            "type": "number",
            "nullable": true
          },
          "hedge_etfs": {
            "type": "object",
            "required": [
              "market",
              "sector",
              "subsector"
            ],
            "properties": {
              "market": {
                "type": "string",
                "example": "SPY"
              },
              "sector": {
                "type": "string",
                "nullable": true
              },
              "subsector": {
                "type": "string",
                "nullable": true
              }
            }
          }
        }
      },
      "HedgeLevelsBlock": {
        "type": "object",
        "description": "Canonical L1/L2/L3 hedge snapshots (additive on metrics, decompose, batch success rows). Prefer this block over parsing flat `l*_mkt_hr` wire keys when comparing cascade levels.\n",
        "required": [
          "L1",
          "L2",
          "L3"
        ],
        "properties": {
          "L1": {
            "$ref": "#/components/schemas/LevelHedgeSnapshot"
          },
          "L2": {
            "$ref": "#/components/schemas/LevelHedgeSnapshot"
          },
          "L3": {
            "$ref": "#/components/schemas/LevelHedgeSnapshot"
          },
          "recommended_level": {
            "type": "string",
            "enum": [
              "L1",
              "L2",
              "L3"
            ],
            "nullable": true,
            "description": "Economic recommendation when emitted (user-segment hedge gate)."
          },
          "statistical_lstar": {
            "type": "string",
            "enum": [
              "L1",
              "L2",
              "L3"
            ],
            "nullable": true,
            "description": "Orthogonal cascade statistical layer pick."
          }
        }
      },
      "DecomposeRequest": {
        "type": "object",
        "description": "Request body for `POST /decompose`.",
        "required": [
          "ticker"
        ],
        "properties": {
          "ticker": {
            "type": "string",
            "description": "Stock ticker symbol (case-insensitive; trimmed and upper-cased).",
            "example": "NVDA"
          }
        }
      },
      "DecomposeLayer": {
        "type": "object",
        "description": "One layer of the four-bet decomposition. `er` is a variance fraction in [0, 1]. `hr` is a dollar-ratio hedge ratio (dollars of ETF per $1 of stock). `residual` has no tradable ETF, so `hr` and `hedge_etf` are null for that layer.\n",
        "required": [
          "er",
          "hr",
          "hedge_etf"
        ],
        "properties": {
          "er": {
            "type": "number",
            "nullable": true,
            "description": "Explained-risk variance fraction for this layer.",
            "example": 0.22
          },
          "hr": {
            "type": "number",
            "nullable": true,
            "description": "Hedge ratio (dollar ratio) for this layer. Null for residual.",
            "example": 0.35
          },
          "hedge_etf": {
            "type": "string",
            "nullable": true,
            "description": "Tradable ETF for this layer. Null for residual.",
            "example": "XLK"
          }
        }
      },
      "DecomposeResponse": {
        "type": "object",
        "description": "Wire response for `POST /decompose`. Semantic four-layer shape; each tradable layer maps to one ETF. The top-level `hedge` map is the negative of each layer's `hr` keyed by ETF (ready to convert into short notionals).\n",
        "required": [
          "ticker",
          "symbol",
          "data_as_of",
          "teo",
          "exposure",
          "hedge"
        ],
        "properties": {
          "ticker": {
            "type": "string",
            "example": "NVDA"
          },
          "symbol": {
            "type": "string",
            "description": "Canonical symbol key in V3 registry."
          },
          "data_as_of": {
            "type": "string",
            "format": "date",
            "description": "Latest trading day the metrics apply to."
          },
          "teo": {
            "type": "string",
            "format": "date",
            "description": "V3 observation date (time-end) for this row."
          },
          "exposure": {
            "type": "object",
            "required": [
              "market",
              "sector",
              "subsector",
              "residual"
            ],
            "properties": {
              "market": {
                "$ref": "#/components/schemas/DecomposeLayer"
              },
              "sector": {
                "$ref": "#/components/schemas/DecomposeLayer"
              },
              "subsector": {
                "$ref": "#/components/schemas/DecomposeLayer"
              },
              "residual": {
                "$ref": "#/components/schemas/DecomposeLayer"
              }
            }
          },
          "hedge": {
            "type": "object",
            "additionalProperties": {
              "type": "number"
            },
            "description": "Map of hedge-ETF → dollar ratio (= negative of the layer `hr`). If two layers share the same ETF (e.g. subsector falls back to the sector ETF), the values are summed so each ETF appears once.\n",
            "example": {
              "SPY": -1.1,
              "XLK": -0.35,
              "SMH": -0.6
            }
          },
          "hedge_levels": {
            "$ref": "#/components/schemas/HedgeLevelsBlock",
            "nullable": true,
            "description": "Canonical L1/L2/L3 compare alongside the L3-focused `exposure` map. Prefer this when the user asks how L1/L2/L3 hedge ratios differ — `exposure` alone is the four-bet semantic view for L3.\n"
          },
          "style": {
            "type": "object",
            "nullable": true,
            "description": "FF2 (SMB + HML) diagnostic split of the `exposure.residual` variance — attribution only, never a tradable hedge (`hedgeable` is always `false`). `explained_variance` is a variance-ratio share on the `hedge_levels` basis named by `hedge_levels.statistical_lstar` (style + `stock_specific` ≈ that level's residual share). See `POST /v4/decompose` for the named-block form of this same split.\n",
            "properties": {
              "explained_variance": {
                "type": "number",
                "nullable": true,
                "example": 0.102
              },
              "hedgeable": {
                "type": "boolean",
                "example": false
              },
              "role": {
                "type": "string",
                "example": "diagnostic"
              }
            }
          },
          "stock_specific": {
            "type": "object",
            "nullable": true,
            "description": "Doubly-cleaned skill residual — `exposure.residual` after the FF2 `style` strip. The stock-selection basis (skills-paper skill metric); not a tradable hedge.\n",
            "properties": {
              "explained_variance": {
                "type": "number",
                "nullable": true,
                "example": 0.475
              },
              "hedgeable": {
                "type": "boolean",
                "example": false
              }
            }
          },
          "_metadata": {
            "type": "object",
            "description": "Lineage, billing code, and freshness (same shape as `/metrics`)."
          },
          "_data_health": {
            "type": "object",
            "properties": {
              "er_populated": {
                "type": "boolean"
              },
              "er_sum": {
                "type": "number",
                "nullable": true,
                "description": "Sum of L3 ER layers (should be ≈ 1 within tolerance)."
              }
            }
          }
        }
      },
      "CanonicalSnapshotPortfolioRequest": {
        "type": "object",
        "description": "Request body for `POST /api/snapshot` when `type` is `portfolio`. Each position must include exactly one of `weight` (fractional or dollar — normalized to weights) or `shares` (converted using latest `price_close`). Do not mix weight-based and share-based rows in one request.\n",
        "required": [
          "type",
          "portfolio"
        ],
        "properties": {
          "type": {
            "type": "string",
            "enum": [
              "portfolio"
            ]
          },
          "portfolio": {
            "type": "array",
            "minItems": 1,
            "maxItems": 100,
            "items": {
              "type": "object",
              "required": [
                "ticker"
              ],
              "properties": {
                "ticker": {
                  "type": "string",
                  "description": "US equity ticker (case-insensitive; normalized to uppercase)."
                },
                "weight": {
                  "type": "number",
                  "format": "float",
                  "exclusiveMinimum": 0,
                  "description": "Positive weight or dollar amount (server normalizes to sum to 1)."
                },
                "shares": {
                  "type": "number",
                  "format": "float",
                  "exclusiveMinimum": 0,
                  "description": "Share count; converted to weights using latest `price_close`."
                }
              }
            }
          },
          "lookback_days": {
            "type": "integer",
            "minimum": 20,
            "maximum": 2000,
            "default": 252,
            "description": "Trading days of history for return curves and attribution series."
          },
          "mode": {
            "type": "string",
            "enum": [
              "frozen"
            ],
            "default": "frozen",
            "description": "Currently only `frozen` (initial weights held through the window)."
          },
          "benchmark": {
            "type": "string",
            "nullable": true,
            "description": "Optional benchmark ticker (reserved)."
          }
        }
      },
      "CanonicalSnapshotTickerRequest": {
        "type": "object",
        "description": "Request body for `POST /api/snapshot` when `type` is `ticker`. Single-name shim that delegates to the same DAL paths used by `/metrics/{ticker}` and `/decompose`, returning the unified `CanonicalSnapshotResponse` shape. Response includes `snapshot.ticker_meta` with the active L3 factors (`[SPY, sector_etf, subsector_etf]` deduped).\n",
        "required": [
          "type",
          "ticker"
        ],
        "properties": {
          "type": {
            "type": "string",
            "enum": [
              "ticker"
            ]
          },
          "ticker": {
            "type": "string",
            "description": "US equity ticker (case-insensitive; normalized to uppercase)."
          },
          "lookback_days": {
            "type": "integer",
            "minimum": 20,
            "maximum": 2000,
            "default": 252,
            "description": "Trading days of history for return curves and attribution series."
          },
          "mode": {
            "type": "string",
            "enum": [
              "frozen"
            ],
            "default": "frozen",
            "description": "Currently only `frozen`."
          },
          "benchmark": {
            "type": "string",
            "nullable": true,
            "description": "Optional benchmark ticker (reserved)."
          }
        }
      },
      "CanonicalSnapshotResponse": {
        "type": "object",
        "description": "Canonical JSON portfolio snapshot: L3 variance decomposition, frozen-weight daily attribution (gross and factor strips), cumulative return and drawdown, and risk summary. Response also includes `_metadata` and `_agent` (see other metered POST routes).\n\nWhen the request supplies a `benchmark` (alias like `SPY` or `70/30`, or a `BW-BENCH-*` id), both `snapshot.benchmark_context_id` and `metadata.benchmark_context_id` carry the resolved `BenchmarkContext` id — the same id consumers can fetch from `/api/data/benchmark/{id}` for methodology / components / `benchmark_kind`. The raw `benchmark` string still echoes back unchanged; unresolved values produce a `null` `benchmark_context_id` (and the raw string is preserved for back-compat).\n",
        "required": [
          "snapshot",
          "time_behavior",
          "attribution",
          "risk_summary",
          "metadata"
        ],
        "properties": {
          "snapshot": {
            "type": "object",
            "additionalProperties": true
          },
          "time_behavior": {
            "type": "object",
            "additionalProperties": true
          },
          "attribution": {
            "type": "object",
            "additionalProperties": true
          },
          "risk_summary": {
            "type": "object",
            "additionalProperties": true
          },
          "metadata": {
            "type": "object",
            "additionalProperties": true
          }
        }
      },
      "FundamentalsRow": {
        "type": "object",
        "description": "One PIT quarterly observation. Derived analytics plus, in sec_facts, raw line items for cells whose serving value is SEC XBRL (public). Any ratio lacking 4 finite trailing quarters, or hitting a guard (equity <= 0, debt <= 0, missing beta), is null. Never clipped.\n",
        "properties": {
          "period_end_date": {
            "type": "string",
            "format": "date",
            "description": "Quarterly reporting period end."
          },
          "filed_date": {
            "type": "string",
            "format": "date",
            "nullable": true,
            "description": "Filing date used for PIT visibility (filed_date <= as_of)."
          },
          "filed_date_source": {
            "type": "string",
            "enum": [
              "exact",
              "approx"
            ],
            "nullable": true,
            "description": "exact = vendor filing date; approx = period_end + 45 days (10-Q deadline) when the vendor date is missing.\n"
          },
          "sec_facts": {
            "type": "object",
            "description": "Raw line items for this period, present only for cells whose serving value is SEC XBRL. Keyed by concept (revenue, net_income, total_equity, cash_from_operations, dividends_paid, share_repurchases, etc.); a concept is absent when its cell is vendor-sourced or empty.\n",
            "additionalProperties": {
              "type": "object",
              "properties": {
                "value": {
                  "type": "number"
                },
                "source": {
                  "type": "string",
                  "enum": [
                    "us_gaap",
                    "ifrs"
                  ]
                }
              }
            }
          },
          "gross_margin": {
            "type": "number",
            "nullable": true,
            "description": "Currently always null — inputs not yet in the store."
          },
          "operating_margin": {
            "type": "number",
            "nullable": true,
            "description": "Currently always null — inputs not yet in the store."
          },
          "roe_ttm": {
            "type": "number",
            "nullable": true,
            "description": "TTM net income over trailing-4-quarter average equity. Null if average equity <= 0."
          },
          "roa_ttm": {
            "type": "number",
            "nullable": true,
            "description": "TTM net income over trailing-4-quarter average total assets."
          },
          "leverage_ratio": {
            "type": "number",
            "nullable": true,
            "description": "Total debt / total equity (book). Null if equity <= 0."
          },
          "fcf_margin": {
            "type": "number",
            "nullable": true,
            "description": "TTM free cash flow (CFO - capex) over TTM revenue."
          },
          "payout_ratio": {
            "type": "number",
            "nullable": true,
            "description": "TTM dividends paid / TTM net income. Null when TTM net income <= 0."
          },
          "retention_ratio": {
            "type": "number",
            "nullable": true,
            "description": "1 - payout_ratio (the reinvestment-rate proxy). Cash basis."
          },
          "buyback_ratio": {
            "type": "number",
            "nullable": true,
            "description": "TTM share repurchases / TTM net income."
          },
          "total_payout_ratio": {
            "type": "number",
            "nullable": true,
            "description": "(TTM dividends paid + share repurchases) / TTM net income. May exceed 1."
          },
          "sustainable_growth": {
            "type": "number",
            "nullable": true,
            "description": "retention_ratio * roe_ttm."
          },
          "equity_bridge_residual": {
            "type": "number",
            "nullable": true,
            "description": "Plug that closes the equity roll-forward by construction (total_equity_t - total_equity_{t-1} = sum of tagged components + residual). Frequently large; a decomposition aid, not a measured line. Null on a symbol's first reported period.\n"
          },
          "equity_bridge_inputs": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "Components that backed the residual. A component MISSING from the list means its movement is inside the residual, not that it was zero.\n"
          },
          "beta_market": {
            "type": "number",
            "nullable": true,
            "description": "ERM3 cascade market beta sampled at the last trade date <= period_end (conditional, short half-life)."
          },
          "beta_sector": {
            "type": "number",
            "nullable": true
          },
          "beta_subsector": {
            "type": "number",
            "nullable": true
          },
          "beta_source": {
            "type": "string",
            "enum": [
              "none",
              "in-universe",
              "out-of-universe",
              "post-delisting"
            ],
            "nullable": true,
            "description": "Beta provenance. Post-delisting betas are never forward-filled."
          },
          "rf_rate": {
            "type": "number",
            "nullable": true,
            "description": "3-month T-bill rate (fraction) at the last observation <= period_end."
          },
          "cost_of_equity": {
            "type": "number",
            "nullable": true,
            "description": "rf_rate + beta_market * erp. Can fall below rf_rate for defensive names (conditional beta)."
          },
          "cost_of_debt": {
            "type": "number",
            "nullable": true,
            "description": "TTM interest expense / total debt. Null if debt <= 0."
          },
          "wacc": {
            "type": "number",
            "nullable": true,
            "description": "BOOK-value-weighted average cost of capital with tax-shielded debt leg."
          },
          "economic_profit": {
            "type": "number",
            "nullable": true,
            "description": "(roe_ttm - cost_of_equity) * total equity (equity-charge form). Null if equity <= 0."
          },
          "market_cap": {
            "type": "number",
            "nullable": true,
            "description": "Reserved. Currently null per row; see the top-level market_cap snapshot."
          }
        }
      },
      "FundamentalsResponse": {
        "type": "object",
        "description": "Wire response for `GET /fundamentals/{ticker}`. Rows are ordered oldest to newest and PIT-filtered by filed_date <= as_of. `disclosures` echoes the parameters and the interpretation caveats.\n",
        "required": [
          "ticker",
          "as_of",
          "rows",
          "disclosures"
        ],
        "properties": {
          "ticker": {
            "type": "string",
            "example": "AAPL"
          },
          "as_of": {
            "type": "string",
            "format": "date"
          },
          "periods_returned": {
            "type": "integer"
          },
          "rows": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/FundamentalsRow"
            }
          },
          "market_cap": {
            "type": "object",
            "description": "Current market cap snapshot (not PIT per quarter).",
            "properties": {
              "value": {
                "type": "number",
                "nullable": true
              },
              "basis": {
                "type": "string",
                "example": "current_snapshot"
              },
              "note": {
                "type": "string"
              }
            }
          },
          "sensitivity_grid": {
            "$ref": "#/components/schemas/FundamentalsSensitivityGrid",
            "nullable": true,
            "description": "Present only when the request set `grid=true`. Null (with a `note`) when no PIT-visible period exists at `as_of`.\n"
          },
          "disclosures": {
            "type": "object",
            "additionalProperties": true,
            "description": "Realized-historical-only statement, ~2009 coverage note, conditional-beta cost-of-equity caveat, book-weight WACC note, TTM convention, and the erp/tax_rate parameter echo.\n"
          },
          "_metadata": {
            "type": "object",
            "additionalProperties": true
          }
        }
      },
      "FundamentalsSensitivityGrid": {
        "type": "object",
        "description": "Cost-of-capital sensitivity grid for the latest PIT-visible period only — `cost_of_equity` / `wacc` / `economic_profit` recomputed across every combination of `erp_values` x `rf_tenor_values`. Pure derived analytics (same fields as `FundamentalsRow`'s cost-of-capital layer), just parameterized instead of single-valued. `cells` is row-major: `cells[i][j]` pairs `erp_values[i]` with `rf_tenor_values[j]`.\n",
        "required": [
          "period_end_date",
          "erp_values",
          "rf_tenor_values",
          "tax_rate",
          "cells"
        ],
        "properties": {
          "period_end_date": {
            "type": "string",
            "format": "date"
          },
          "filed_date": {
            "type": "string",
            "format": "date",
            "nullable": true
          },
          "erp_values": {
            "type": "array",
            "items": {
              "type": "number"
            }
          },
          "rf_tenor_values": {
            "type": "array",
            "items": {
              "type": "string",
              "enum": [
                "3m",
                "1y",
                "2y",
                "5y",
                "10y",
                "30y"
              ]
            }
          },
          "tax_rate": {
            "type": "number"
          },
          "cells": {
            "type": "array",
            "description": "Row-major [erp_values.length][rf_tenor_values.length].",
            "items": {
              "type": "array",
              "items": {
                "type": "object",
                "properties": {
                  "cost_of_equity": {
                    "type": "number",
                    "nullable": true
                  },
                  "wacc": {
                    "type": "number",
                    "nullable": true
                  },
                  "economic_profit": {
                    "type": "number",
                    "nullable": true
                  }
                }
              }
            }
          }
        }
      },
      "MetricsSnapshotResponse": {
        "type": "object",
        "description": "Wire response for `GET /metrics/{ticker}`. Numeric fields live under `metrics`; `teo` is the observation date (V3 time-end).\n",
        "required": [
          "symbol",
          "ticker",
          "teo",
          "periodicity",
          "metrics",
          "display",
          "meta"
        ],
        "properties": {
          "symbol": {
            "type": "string",
            "description": "Canonical symbol key in V3 registry."
          },
          "ticker": {
            "type": "string",
            "example": "NVDA"
          },
          "teo": {
            "type": "string",
            "format": "date",
            "description": "Latest observation date for the metrics row."
          },
          "periodicity": {
            "type": "string",
            "example": "daily"
          },
          "metrics": {
            "$ref": "#/components/schemas/MetricsV3"
          },
          "display": {
            "type": "object",
            "additionalProperties": {
              "type": "string"
            },
            "description": "Optional display labels for metric keys (e.g. UI column titles)."
          },
          "meta": {
            "type": "object",
            "properties": {
              "sector_etf": {
                "type": "string",
                "nullable": true
              },
              "asset_type": {
                "type": "string",
                "nullable": true
              }
            }
          },
          "hedge_levels": {
            "$ref": "#/components/schemas/HedgeLevelsBlock",
            "nullable": true,
            "description": "Canonical L1/L2/L3 hedge snapshots for cross-level comparison."
          }
        }
      },
      "TickerReturnsDailyRow": {
        "type": "object",
        "description": "One trading day in `GET /ticker-returns` `data` array. Stock rows populate the full V3 security_history field set below. **ETF rows are a strict subset — only `date`, `returns_gross`, and `price_close` are emitted** (the L1/L2/L3 hedge/ER properties are omitted entirely from ETF rows rather than returned as `null`, since ETFs are not factor-decomposed). Branch on the response's top-level `asset_type` before reading L* fields.\n",
        "properties": {
          "date": {
            "type": "string",
            "format": "date"
          },
          "returns_gross": {
            "type": "number",
            "format": "float",
            "nullable": true,
            "description": "Daily gross return of the stock. Null when rolling-window metrics are unavailable (trailing data)."
          },
          "price_close": {
            "type": "number",
            "format": "float",
            "nullable": true,
            "description": "End-of-day price when available from history."
          },
          "l3_mkt_hr": {
            "type": "number",
            "format": "float",
            "nullable": true,
            "description": "Rolling L3 market hedge ratio (SPY notional per $1 stock). Null in trailing rows where the rolling regression window has insufficient data. May be negative (orthogonalization); negative market HR at L2/L3 is common.\n"
          },
          "l3_sec_hr": {
            "type": "number",
            "format": "float",
            "nullable": true,
            "description": "Rolling L3 sector ETF hedge ratio (dollar_ratio). Null when the rolling window has insufficient data. May be negative due to orthogonalization (factor neutralization); not a sign error in the data.\n"
          },
          "l3_sub_hr": {
            "type": "number",
            "format": "float",
            "nullable": true,
            "description": "Rolling L3 subsector ETF hedge ratio (dollar_ratio). Null when the rolling window has insufficient data. May be negative (orthogonalization or long subsector ETF leg); not a sign error in the data.\n"
          },
          "l3_mkt_er": {
            "type": "number",
            "format": "float",
            "nullable": true,
            "description": "Market variance share at L3 (explained risk, 0–1). Null in trailing rows where the rolling window has insufficient data.\n"
          },
          "l3_sec_er": {
            "type": "number",
            "format": "float",
            "nullable": true,
            "description": "Sector variance share at L3 (explained risk, 0–1). Null when the rolling window has insufficient data.\n"
          },
          "l3_sub_er": {
            "type": "number",
            "format": "float",
            "nullable": true,
            "description": "Subsector variance share at L3 (explained risk, 0–1). Null when the rolling window has insufficient data.\n"
          },
          "l3_res_er": {
            "type": "number",
            "format": "float",
            "nullable": true,
            "description": "Idiosyncratic (residual) variance share at L3 (0–1); SDK semantic name `l3_residual_er`. Null when the rolling window has insufficient data.\n"
          }
        }
      },
      "GrossReturnDailyRow": {
        "type": "object",
        "description": "One trading day in Parquet/CSV for `GET /returns` and `GET /etf-returns`: long table with the same fields as zipping JSON parallel arrays `dates` and `returns_gross` by index.\n",
        "properties": {
          "date": {
            "type": "string",
            "format": "date"
          },
          "returns_gross": {
            "type": "number",
            "format": "float",
            "nullable": true,
            "description": "Daily gross return (same series as JSON `returns_gross` array)."
          }
        }
      },
      "BatchAnalyzeExportRow": {
        "type": "object",
        "description": "One row in Parquet/CSV from `POST /batch/analyze` when `format` is `parquet` or `csv`. Emitted only for tickers that succeeded with `returns` data; tickers with errors are omitted.\n",
        "properties": {
          "ticker": {
            "type": "string"
          },
          "date": {
            "type": "string",
            "format": "date"
          },
          "gross_return": {
            "type": "number",
            "format": "float",
            "nullable": true,
            "description": "Daily gross return (JSON `returns.values` at the same index as `date`)."
          },
          "l1": {
            "type": "number",
            "format": "float",
            "nullable": true,
            "description": "Rolling L3 market HR history (`returns.l1` in JSON)."
          },
          "l2": {
            "type": "number",
            "format": "float",
            "nullable": true,
            "description": "Rolling L3 sector HR history (`returns.l2` in JSON)."
          },
          "l3": {
            "type": "number",
            "format": "float",
            "nullable": true,
            "description": "Rolling L3 subsector HR history (`returns.l3` in JSON)."
          }
        }
      },
      "TickerReturnsResponseV3": {
        "type": "object",
        "required": [
          "symbol",
          "ticker",
          "periodicity",
          "data",
          "meta"
        ],
        "properties": {
          "symbol": {
            "type": "string"
          },
          "ticker": {
            "type": "string"
          },
          "asset_type": {
            "type": "string",
            "enum": [
              "stock",
              "etf"
            ],
            "description": "Returned for every response. ETF responses populate `date`, `returns_gross`, and `price_close` only; all L1/L2/L3 columns are `null` because ETFs are not factor-decomposed.\n"
          },
          "periodicity": {
            "type": "string",
            "example": "daily"
          },
          "data": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/TickerReturnsDailyRow"
            }
          },
          "meta": {
            "$ref": "#/components/schemas/TickerReturnsMeta"
          }
        }
      },
      "TickerReturnsMeta": {
        "type": "object",
        "properties": {
          "market_etf": {
            "type": "string",
            "example": "SPY"
          },
          "sector_etf": {
            "type": "string",
            "nullable": true,
            "description": "Null for ETF tickers (e.g. `SPY` itself).",
            "example": "SOXX"
          },
          "subsector_etf": {
            "type": "string",
            "nullable": true,
            "example": "XSD"
          },
          "universe": {
            "type": "string",
            "description": "`US_EQUITY` for stocks, `US_ETF` for ETFs.",
            "example": "US_EQUITY"
          }
        }
      },
      "BatchHedgeRatios": {
        "type": "object",
        "description": "Present when `metrics` includes `hedge_ratios`. Dollar HRs per $1 stock (same economics as `full_metrics` `*_hr` fields). Property names are **short** (`l1_market`, …); ERM3 zarr hedge-weight naming `L1_market_HR` … `L3_subsector_HR` maps row-wise to these keys — see docs/ERM3_ZARR_API_PARITY.md.\n",
        "properties": {
          "l1_market": {
            "type": "number",
            "format": "float",
            "nullable": true,
            "description": "Zarr-style alias `L1_market_HR`; same as `full_metrics.l1_market_hr`."
          },
          "l2_market": {
            "type": "number",
            "format": "float",
            "nullable": true,
            "description": "Zarr-style alias `L2_market_HR`; same as `full_metrics.l2_market_hr`."
          },
          "l2_sector": {
            "type": "number",
            "format": "float",
            "nullable": true,
            "description": "Zarr-style alias `L2_sector_HR`; same as `full_metrics.l2_sector_hr`."
          },
          "l3_market": {
            "type": "number",
            "format": "float",
            "nullable": true,
            "description": "Zarr-style alias `L3_market_HR`; same as `full_metrics.l3_market_hr`."
          },
          "l3_sector": {
            "type": "number",
            "format": "float",
            "nullable": true,
            "description": "Zarr-style alias `L3_sector_HR`; same as `full_metrics.l3_sector_hr`."
          },
          "l3_subsector": {
            "type": "number",
            "format": "float",
            "nullable": true,
            "description": "Zarr-style alias `L3_subsector_HR`; same as `full_metrics.l3_subsector_hr`."
          }
        }
      },
      "BatchReturnsPayload": {
        "type": "object",
        "description": "Present when `metrics` includes `returns`. Arrays are parallel by index. Fields `l1`, `l2`, `l3` hold daily rolling hedge ratios from V3 (`l3_mkt_hr`, `l3_sec_hr`, `l3_sub_hr` history), not combined L1/L2/L3 model levels.\n",
        "properties": {
          "dates": {
            "type": "array",
            "items": {
              "type": "string",
              "format": "date"
            }
          },
          "values": {
            "type": "array",
            "items": {
              "type": "number"
            },
            "description": "Daily gross returns (`returns_gross`)."
          },
          "l1": {
            "type": "array",
            "items": {
              "type": "number",
              "nullable": true
            }
          },
          "l2": {
            "type": "array",
            "items": {
              "type": "number",
              "nullable": true
            }
          },
          "l3": {
            "type": "array",
            "items": {
              "type": "number",
              "nullable": true
            }
          }
        }
      },
      "BatchFullMetrics": {
        "type": "object",
        "description": "Present when `metrics` includes `full_metrics`. Flat snapshot for batch consumers: L1/L2/L3 explained-risk and hedge-ratio fields when available from the backend (see docs/ERM3_ZARR_API_PARITY.md for zarr `L*_ER` / `L*_HR` mapping). Zarr topic-level `wmean_*` features are computed **client-side** as holdings-weighted means of these fields. Nulls mean missing or not modelled for that ticker — not omitted keys. Hedge ratios may be negative at any level (orthogonalization); negative **market** HR at L2 or L3 is especially common.\n",
        "properties": {
          "ticker": {
            "type": "string"
          },
          "date": {
            "type": "string",
            "format": "date",
            "nullable": true,
            "description": "Observation date (`teo`) for the snapshot; align with `X-Data-As-Of` / `_metadata.data_as_of`."
          },
          "volatility": {
            "type": "number",
            "format": "float",
            "nullable": true
          },
          "l1_market_hr": {
            "type": "number",
            "format": "float",
            "nullable": true,
            "description": "Zarr-style `L1_market_HR`; mirrors `hedge_ratios.l1_market`."
          },
          "l2_market_hr": {
            "type": "number",
            "format": "float",
            "nullable": true,
            "description": "Zarr-style `L2_market_HR`; mirrors `hedge_ratios.l2_market`."
          },
          "l2_sector_hr": {
            "type": "number",
            "format": "float",
            "nullable": true,
            "description": "Zarr-style `L2_sector_HR`; mirrors `hedge_ratios.l2_sector`."
          },
          "l3_market_hr": {
            "type": "number",
            "format": "float",
            "nullable": true,
            "description": "Zarr-style `L3_market_HR`; mirrors `hedge_ratios.l3_market`."
          },
          "l3_sector_hr": {
            "type": "number",
            "format": "float",
            "nullable": true,
            "description": "Zarr-style `L3_sector_HR`; mirrors `hedge_ratios.l3_sector`."
          },
          "l3_subsector_hr": {
            "type": "number",
            "format": "float",
            "nullable": true,
            "description": "Zarr-style `L3_subsector_HR`; mirrors `hedge_ratios.l3_subsector`."
          },
          "l1_market_er": {
            "type": "number",
            "format": "float",
            "nullable": true,
            "description": "Zarr-style `L1_market_ER`."
          },
          "l1_residual_er": {
            "type": "number",
            "format": "float",
            "nullable": true,
            "description": "Zarr-style `L1_residual_ER`."
          },
          "l2_market_er": {
            "type": "number",
            "format": "float",
            "nullable": true,
            "description": "Zarr-style `L2_market_ER`."
          },
          "l2_sector_er": {
            "type": "number",
            "format": "float",
            "nullable": true,
            "description": "Zarr-style `L2_sector_ER`."
          },
          "l2_residual_er": {
            "type": "number",
            "format": "float",
            "nullable": true,
            "description": "Zarr-style `L2_residual_ER`."
          },
          "l3_market_er": {
            "type": "number",
            "format": "float",
            "nullable": true,
            "description": "Zarr-style `L3_market_ER`."
          },
          "l3_sector_er": {
            "type": "number",
            "format": "float",
            "nullable": true,
            "description": "Zarr-style `L3_sector_ER`."
          },
          "l3_subsector_er": {
            "type": "number",
            "format": "float",
            "nullable": true,
            "description": "Zarr-style `L3_subsector_ER`."
          },
          "l3_residual_er": {
            "type": "number",
            "format": "float",
            "nullable": true,
            "description": "Zarr-style `L3_residual_ER`."
          },
          "market_cap": {
            "type": "number",
            "format": "float",
            "nullable": true
          },
          "close_price": {
            "type": "number",
            "format": "float",
            "nullable": true
          }
        }
      },
      "BatchAnalyzeTickerResult": {
        "type": "object",
        "description": "One entry in `POST /batch/analyze` `results` map (keyed by upper-case ticker).",
        "required": [
          "ticker",
          "status"
        ],
        "properties": {
          "ticker": {
            "type": "string"
          },
          "status": {
            "type": "string",
            "enum": [
              "success",
              "error"
            ]
          },
          "error": {
            "type": "string",
            "description": "Present when status is `error`."
          },
          "returns": {
            "$ref": "#/components/schemas/BatchReturnsPayload"
          },
          "hedge_ratios": {
            "$ref": "#/components/schemas/BatchHedgeRatios"
          },
          "full_metrics": {
            "$ref": "#/components/schemas/BatchFullMetrics"
          },
          "meta": {
            "type": "object",
            "nullable": true,
            "properties": {
              "market_etf": {
                "type": "string",
                "example": "SPY"
              },
              "sector_etf": {
                "type": "string",
                "nullable": true
              },
              "subsector_etf": {
                "type": "string",
                "nullable": true
              }
            }
          },
          "hedge_levels": {
            "$ref": "#/components/schemas/HedgeLevelsBlock",
            "nullable": true,
            "description": "Present when status is success — canonical L1/L2/L3 block for this ticker row."
          }
        }
      },
      "BatchAnalyzeResponse": {
        "type": "object",
        "required": [
          "results",
          "summary",
          "_agent"
        ],
        "properties": {
          "results": {
            "type": "object",
            "additionalProperties": {
              "$ref": "#/components/schemas/BatchAnalyzeTickerResult"
            }
          },
          "summary": {
            "type": "object",
            "properties": {
              "total": {
                "type": "integer"
              },
              "success": {
                "type": "integer"
              },
              "errors": {
                "type": "integer"
              }
            }
          },
          "_agent": {
            "$ref": "#/components/schemas/AgentMeta"
          },
          "_metadata": {
            "$ref": "#/components/schemas/RiskMetadata",
            "description": "Lineage for PIT tests: model version, as-of date, factor set, universe size. Also emitted as `X-Risk-Model-Version`, `X-Data-As-Of`, `X-Factor-Set-Id`, `X-Universe-Size` headers when the gateway attaches them.\n"
          }
        }
      },
      "L3DecompositionResponse": {
        "type": "object",
        "description": "Columnar L3 decomposition from V3 `security_history` (daily points; arrays are parallel by index). Field names use readable suffixes (`l3_market_er`, …). No `_agent` block in the current response body.\n",
        "required": [
          "ticker",
          "dates",
          "l3_market_hr",
          "l3_sector_hr",
          "l3_subsector_hr",
          "l3_market_er",
          "l3_sector_er",
          "l3_subsector_er",
          "l3_residual_er",
          "market_factor_etf",
          "universe",
          "data_source"
        ],
        "properties": {
          "ticker": {
            "type": "string"
          },
          "dates": {
            "type": "array",
            "items": {
              "type": "string",
              "format": "date"
            }
          },
          "l3_market_hr": {
            "type": "array",
            "items": {
              "type": "number",
              "format": "float",
              "nullable": true
            }
          },
          "l3_sector_hr": {
            "type": "array",
            "items": {
              "type": "number",
              "format": "float",
              "nullable": true
            }
          },
          "l3_subsector_hr": {
            "type": "array",
            "items": {
              "type": "number",
              "format": "float",
              "nullable": true
            }
          },
          "l3_market_er": {
            "type": "array",
            "items": {
              "type": "number",
              "format": "float",
              "nullable": true
            }
          },
          "l3_sector_er": {
            "type": "array",
            "items": {
              "type": "number",
              "format": "float",
              "nullable": true
            }
          },
          "l3_subsector_er": {
            "type": "array",
            "items": {
              "type": "number",
              "format": "float",
              "nullable": true
            }
          },
          "l3_residual_er": {
            "type": "array",
            "items": {
              "type": "number",
              "format": "float",
              "nullable": true
            }
          },
          "market_factor_etf": {
            "type": "string"
          },
          "universe": {
            "type": "string"
          },
          "data_source": {
            "type": "string",
            "example": "security_history"
          }
        }
      },
      "ReturnsDecompositionResponse": {
        "type": "object",
        "description": "Daily gross return and L1/L2/L3 factor, combined-factor, and residual return series from ds_erm3_returns (zarr SSOT). Optional Lstar dispatch adds per-date recommended hedge level and Lstar residual return.\n",
        "properties": {
          "ticker": {
            "type": "string"
          },
          "dates": {
            "type": "array",
            "items": {
              "type": "string",
              "format": "date"
            }
          },
          "gross_return": {
            "type": "array",
            "items": {
              "type": "number",
              "format": "float",
              "nullable": true
            }
          },
          "l1_factor_return": {
            "type": "array",
            "items": {
              "type": "number",
              "format": "float",
              "nullable": true
            }
          },
          "l2_factor_return": {
            "type": "array",
            "items": {
              "type": "number",
              "format": "float",
              "nullable": true
            }
          },
          "l3_factor_return": {
            "type": "array",
            "items": {
              "type": "number",
              "format": "float",
              "nullable": true
            }
          },
          "l1_combined_factor_return": {
            "type": "array",
            "items": {
              "type": "number",
              "format": "float",
              "nullable": true
            }
          },
          "l2_combined_factor_return": {
            "type": "array",
            "items": {
              "type": "number",
              "format": "float",
              "nullable": true
            }
          },
          "l3_combined_factor_return": {
            "type": "array",
            "items": {
              "type": "number",
              "format": "float",
              "nullable": true
            }
          },
          "l1_residual_return": {
            "type": "array",
            "items": {
              "type": "number",
              "format": "float",
              "nullable": true
            }
          },
          "l2_residual_return": {
            "type": "array",
            "items": {
              "type": "number",
              "format": "float",
              "nullable": true
            }
          },
          "l3_residual_return": {
            "type": "array",
            "items": {
              "type": "number",
              "format": "float",
              "nullable": true
            }
          },
          "lstar": {
            "type": "array",
            "items": {
              "type": "string",
              "enum": [
                "L1",
                "L2",
                "L3"
              ],
              "nullable": true
            },
            "description": "Present when include_lstar=true or dispatch=lstar."
          },
          "lstar_residual_return": {
            "type": "array",
            "items": {
              "type": "number",
              "format": "float",
              "nullable": true
            }
          },
          "threshold_used": {
            "type": "number",
            "format": "float",
            "description": "Marginal ER threshold used for Lstar dispatch (default 0.01)."
          },
          "market_factor_etf": {
            "type": "string"
          },
          "universe": {
            "type": "string"
          },
          "data_source": {
            "type": "string",
            "example": "zarr"
          }
        }
      },
      "IndustryPanelRow": {
        "type": "object",
        "required": [
          "industry_code",
          "level"
        ],
        "properties": {
          "industry_code": {
            "type": "integer",
            "description": "EODHD 4-digit industry code (FactSet fs_industry_code)."
          },
          "level": {
            "type": "string",
            "enum": [
              "market",
              "sector",
              "subsector"
            ]
          },
          "beta_mean": {
            "type": "number",
            "format": "float",
            "nullable": true,
            "description": "Log-mcap-weighted typical factor β for the industry at this cascade level."
          },
          "beta_variance": {
            "type": "number",
            "format": "float",
            "nullable": true,
            "description": "Cross-sectional dispersion of peer βs (τ²)."
          },
          "n_companies": {
            "type": "integer",
            "nullable": true,
            "description": "Peer count in the industry cohort."
          },
          "total_log_mcap_weight": {
            "type": "number",
            "format": "float",
            "nullable": true,
            "description": "Sum of log(mcap+1) across peers."
          },
          "n_facts": {
            "type": "integer",
            "description": "Count of cascade facts that contributed to this level-row after min_peers. Present on by=level rows. Always 1 on a level-keyed vintage; >1 when a fact-keyed store collapsed multiple facts.\n"
          },
          "fact": {
            "type": "string",
            "description": "ETF ticker of the cascade fact. Present on by=fact rows."
          }
        }
      },
      "IndustryPanelResponse": {
        "type": "object",
        "description": "Cross-section of Vasicek industry peer β statistics from ds_erm3_industry at one observation date (default latest teo). Default by=level keeps one row per (industry_code, level). by=fact emits one row per (industry, fact). Multi-fact cells are historical (last L3 day 2021-06-22).\n",
        "properties": {
          "teo": {
            "type": "string",
            "format": "date"
          },
          "industries": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/IndustryPanelRow"
            }
          },
          "level": {
            "type": "string",
            "enum": [
              "market",
              "sector",
              "subsector"
            ],
            "description": "Present when the request filtered to a single cascade level."
          },
          "by": {
            "type": "string",
            "enum": [
              "level",
              "fact"
            ],
            "description": "Requested grouping. Default level."
          },
          "panel_key": {
            "type": "string",
            "enum": [
              "level",
              "fact"
            ],
            "description": "Store vintage — level is the pre-rekey collapse; fact is the per-fact axis."
          },
          "min_peers": {
            "type": "integer",
            "description": "Minimum n_companies filter applied (from request or zarr min_peers attr)."
          },
          "market_factor_etf": {
            "type": "string"
          },
          "universe": {
            "type": "string"
          },
          "data_source": {
            "type": "string",
            "example": "zarr"
          }
        }
      },
      "PeerMember": {
        "type": "object",
        "required": [
          "ticker",
          "symbol"
        ],
        "properties": {
          "ticker": {
            "type": "string",
            "example": "AMD"
          },
          "company_name": {
            "type": "string",
            "nullable": true
          },
          "market_cap": {
            "type": "number",
            "nullable": true
          },
          "sector_etf": {
            "type": "string",
            "nullable": true,
            "example": "XLK"
          },
          "subsector_etf": {
            "type": "string",
            "nullable": true,
            "example": "SOXX"
          },
          "symbol": {
            "type": "string",
            "description": "Internal bw_sym_id (not a public join key for clients)."
          }
        }
      },
      "ArtifactCapabilityResponse": {
        "type": "object",
        "required": [
          "derived_from",
          "subject_kinds",
          "params",
          "pairs",
          "unavailable",
          "counts"
        ],
        "properties": {
          "derived_from": {
            "type": "string",
            "description": "The source constant the response is computed from.",
            "example": "lib/artifacts/render-client.ts::ARTIFACT_RENDER_CAPABILITY"
          },
          "discovery": {
            "type": "object",
            "description": "Routes to call before forming a render request.",
            "properties": {
              "capability": {
                "type": "string",
                "example": "GET /api/artifacts/capability?subject_kind=&slug="
              },
              "as_of": {
                "type": "string",
                "example": "GET /api/artifacts/as-of?slug=&subject_id=&version="
              }
            }
          },
          "subject_kinds": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "Every kind render-svc resolves from a subject_id prefix."
          },
          "prerendered_subject_kinds": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "Kinds render-svc can only read from the store. See each pair's `as_of_latest` for what `latest` does; `GET /api/artifacts/as-of` lists the dates that exist.\n"
          },
          "store_resolved_latest_kinds": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "Pre-rendered kinds whose `as_of=latest` resolves to the newest stored vintage (cohorts).\n"
          },
          "unimplemented_subject_kinds": {
            "type": "object",
            "additionalProperties": {
              "type": "string"
            },
            "description": "Kinds resolvable from an id prefix that no loader serves, keyed to the reason. Present so a zero-capability kind reads as measured rather than unaudited.\n"
          },
          "params": {
            "type": "object",
            "properties": {
              "vocabulary": {
                "type": "array",
                "items": {
                  "type": "string"
                },
                "description": "Every key the render request schema accepts."
              },
              "by_slug": {
                "type": "object",
                "additionalProperties": {
                  "type": "array",
                  "items": {
                    "type": "string"
                  }
                },
                "description": "Applicability per slug. A slug absent from this map accepts no params. Applicability is not the accepted value set — the artifact module narrows further and answers 422 on a value it does not take.\n"
              }
            }
          },
          "pairs": {
            "type": "array",
            "description": "Verified pairs only. Nothing else may appear here.",
            "items": {
              "type": "object",
              "required": [
                "slug",
                "version",
                "subject_kind",
                "params",
                "prerendered"
              ],
              "properties": {
                "slug": {
                  "type": "string"
                },
                "version": {
                  "type": "string"
                },
                "subject_kind": {
                  "type": "string"
                },
                "params": {
                  "type": "array",
                  "items": {
                    "type": "string"
                  }
                },
                "prerendered": {
                  "type": "boolean"
                },
                "as_of_latest": {
                  "type": "string",
                  "enum": [
                    "loader",
                    "newest_prerendered",
                    "unsupported"
                  ],
                  "description": "How `as_of=latest` resolves for this pair. `newest_prerendered` reads the newest date from `GET /api/artifacts/as-of`; `unsupported` means pass one of those dates explicitly.\n"
                },
                "notes": {
                  "type": "string"
                }
              }
            }
          },
          "unavailable": {
            "type": "array",
            "description": "Pairs measured against prod and refused, with the reason. Recorded rather than deleted so a later audit does not re-add them hopefully.\n",
            "items": {
              "type": "object",
              "required": [
                "slug",
                "subject_kind",
                "status",
                "reason"
              ],
              "properties": {
                "slug": {
                  "type": "string"
                },
                "subject_kind": {
                  "type": "string"
                },
                "status": {
                  "type": "string",
                  "enum": [
                    "degraded",
                    "unavailable"
                  ]
                },
                "reason": {
                  "type": "string"
                }
              }
            }
          },
          "counts": {
            "type": "object",
            "properties": {
              "verified": {
                "type": "integer"
              },
              "unavailable": {
                "type": "integer"
              },
              "slugs": {
                "type": "integer"
              }
            }
          }
        }
      },
      "ArtifactAsOfResponse": {
        "type": "object",
        "required": [
          "slug",
          "version",
          "subject_id",
          "subject_kind",
          "as_of_latest",
          "latest",
          "as_of",
          "vintages",
          "count"
        ],
        "properties": {
          "slug": {
            "type": "string",
            "example": "risk_dna_stacked"
          },
          "version": {
            "type": "string",
            "example": "v1"
          },
          "subject_id": {
            "type": "string",
            "description": "The id as requested.",
            "example": "BW-COHORT-RES-MAG7"
          },
          "subject_kind": {
            "type": "string",
            "example": "cohort"
          },
          "canonical_subject_id": {
            "type": "string",
            "description": "The canonical spelling (differs from `subject_id` only for filers)."
          },
          "subject_id_spellings_searched": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "as_of_latest": {
            "type": "string",
            "enum": [
              "loader",
              "newest_prerendered",
              "unsupported"
            ],
            "description": "What `as_of=latest` does on the render call for this subject kind: `newest_prerendered` (cohorts — the newest date below), `unsupported` (filers — pass a date from `as_of`), or `loader` (live data; this listing is informational).\n"
          },
          "latest": {
            "type": "string",
            "format": "date",
            "description": "Newest pre-rendered date.",
            "example": "2026-04-21"
          },
          "as_of": {
            "type": "array",
            "description": "Date-only view of `vintages`, oldest first.",
            "items": {
              "type": "string",
              "format": "date"
            }
          },
          "vintages": {
            "type": "array",
            "items": {
              "type": "object",
              "required": [
                "as_of",
                "formats",
                "params_variants",
                "gcs_path"
              ],
              "properties": {
                "as_of": {
                  "type": "string",
                  "format": "date"
                },
                "formats": {
                  "type": "array",
                  "description": "Formats stored for the default render (no params).",
                  "items": {
                    "type": "string"
                  },
                  "example": [
                    "json",
                    "png"
                  ]
                },
                "params_variants": {
                  "type": "array",
                  "description": "Params fragments stored alongside, e.g. `peer_n-5`.",
                  "items": {
                    "type": "string"
                  }
                },
                "gcs_path": {
                  "type": "object",
                  "description": "Object URI per default-render format.",
                  "additionalProperties": {
                    "type": "string"
                  }
                }
              }
            }
          },
          "count": {
            "type": "integer"
          }
        }
      },
      "PeersResponse": {
        "type": "object",
        "required": [
          "ticker",
          "target",
          "group_by",
          "group_etf",
          "peers",
          "peer_count"
        ],
        "properties": {
          "ticker": {
            "type": "string",
            "example": "NVDA"
          },
          "target": {
            "$ref": "#/components/schemas/PeerMember"
          },
          "group_by": {
            "type": "string",
            "enum": [
              "subsector_etf",
              "sector_etf"
            ]
          },
          "group_etf": {
            "type": "string",
            "example": "SOXX"
          },
          "peers": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/PeerMember"
            }
          },
          "peer_count": {
            "type": "integer"
          },
          "warnings": {
            "type": "array",
            "items": {
              "type": "string"
            }
          }
        }
      },
      "CohortDisclosures": {
        "type": "object",
        "description": "Interpretation notes that govern correct use of the cohort statistics. `no_intercept_contract` and `return_source_legend` are read from the store's own attributes rather than restated here, so they cannot drift from the data they describe.\n",
        "properties": {
          "no_intercept_contract": {
            "type": "string",
            "description": "ERM3 residuals are estimated WITHOUT an intercept and therefore retain each stock's alpha. The cross-sectional mean is NOT zero. If you are building relative-ranking signals, demean first using `residual_mean`.\n"
          },
          "return_source_legend": {
            "type": "string",
            "description": "Meaning of each `factor_source` code."
          },
          "coverage": {
            "type": "string",
            "description": "Cohort coverage of the eligible universe (~88%) and the date from which full factor richness begins.\n"
          },
          "dispersion_use": {
            "type": "string",
            "description": "How to use `residual_sd` — a conditioning and allocation variable, not an alpha source.\n"
          },
          "er_sign": {
            "type": "string",
            "description": "Why `cohort_ER` is an incremental attribution that can be negative, and how it differs from `linked_beta_r2`.\n"
          },
          "thin_cohorts": {
            "type": "string",
            "description": "Why to filter on `min_names` and prefer `n_effective` for breadth."
          }
        }
      },
      "CohortStatistics": {
        "type": "object",
        "description": "Cohort statistics for one (teo, cohort) cell. Keys present depend on the `variables` requested. Values are null where the statistic is undefined for that day.\n",
        "additionalProperties": {
          "type": "number",
          "nullable": true
        },
        "properties": {
          "residual_mean": {
            "type": "number",
            "nullable": true,
            "description": "Equal-weighted mean member residual — the quantity to subtract when demeaning a relative-ranking signal at this level.\n"
          },
          "residual_mean_cw": {
            "type": "number",
            "nullable": true,
            "description": "Cap-weighted equivalent; the EW/CW wedge is itself informative."
          },
          "residual_sd": {
            "type": "number",
            "nullable": true,
            "description": "Cross-sectional dispersion of member residuals — how much selection opportunity exists in the cohort. Read with `mean_pairwise_corr`.\n"
          },
          "residual_skew": {
            "type": "number",
            "nullable": true
          },
          "residual_p10": {
            "type": "number",
            "nullable": true
          },
          "residual_p90": {
            "type": "number",
            "nullable": true
          },
          "mean_pairwise_corr": {
            "type": "number",
            "nullable": true,
            "description": "Mean pairwise correlation of member residuals, from a 63-day identity-based estimator. An estimate — do not present to high precision.\n"
          },
          "n_names": {
            "type": "integer",
            "nullable": true,
            "description": "Member count that day. Guards every other statistic."
          },
          "n_effective": {
            "type": "number",
            "nullable": true,
            "description": "Inverse-Herfindahl breadth. Prefer this over `n_names` for anything power- or breadth-related — a cohort with one dominant constituent has far less effective breadth than its headcount suggests.\n"
          },
          "weight_top1": {
            "type": "number",
            "nullable": true
          },
          "membership_churn": {
            "type": "integer",
            "nullable": true
          },
          "linked_beta": {
            "type": "number",
            "nullable": true,
            "description": "Beta of this cohort's factor to its parent's."
          },
          "link_fit_resid_sd": {
            "type": "number",
            "nullable": true,
            "description": "Residual standard deviation of the 252-day link regression (cohort factor on its parent), in daily-return units. A fit-quality / dispersion measure — NOT a standard error of `linked_beta`; do not build confidence intervals from it.\n"
          },
          "linked_beta_r2": {
            "type": "number",
            "nullable": true,
            "description": "R² of the cohort factor on its parent. NOT the same quantity as `cohort_ER`.\n"
          },
          "linked_beta_roll63": {
            "type": "number",
            "nullable": true,
            "description": "63-day variant; instability in this is itself informative."
          },
          "cohort_factor_return": {
            "type": "number",
            "nullable": true
          },
          "cohort_residual_return": {
            "type": "number",
            "nullable": true,
            "description": "Factor return net of `linked_beta` × parent return."
          },
          "cohort_ER": {
            "type": "number",
            "nullable": true,
            "description": "Mean member's explained risk attributed to this level. An incremental attribution, not an R² share — it can be slightly negative and does not sum to 1.\n"
          },
          "factor_source": {
            "type": "integer",
            "nullable": true,
            "description": "Provenance of the factor return that day. 0 = native; non-zero means a substitute instrument backed it. See `return_source_legend`.\n"
          }
        }
      },
      "CohortCrossSectionRow": {
        "type": "object",
        "properties": {
          "ticker": {
            "type": "string",
            "description": "Cohort label (SPY or a GICS sector SPDR)."
          },
          "level": {
            "type": "integer",
            "enum": [
              1,
              2
            ],
            "description": "1 = market, 2 = GICS sector."
          },
          "parent": {
            "type": "string",
            "nullable": true,
            "description": "Parent cohort's ticker; null for the market cohort."
          },
          "values": {
            "$ref": "#/components/schemas/CohortStatistics"
          }
        }
      },
      "CohortCrossSectionResponse": {
        "type": "object",
        "description": "Cohort statistics across cohorts at one observation date.",
        "properties": {
          "teo": {
            "type": "string",
            "format": "date"
          },
          "cohorts": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/CohortCrossSectionRow"
            }
          },
          "variables": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "min_names": {
            "type": "integer"
          },
          "universe": {
            "type": "string"
          },
          "market_factor_etf": {
            "type": "string"
          },
          "store_build": {
            "type": "string",
            "nullable": true,
            "description": "Build timestamp of the underlying store."
          },
          "disclosures": {
            "$ref": "#/components/schemas/CohortDisclosures"
          },
          "data_source": {
            "type": "string",
            "example": "zarr"
          }
        }
      },
      "CohortSeriesPoint": {
        "type": "object",
        "properties": {
          "date": {
            "type": "string",
            "format": "date"
          },
          "values": {
            "$ref": "#/components/schemas/CohortStatistics"
          },
          "proxy_source": {
            "type": "string",
            "nullable": true,
            "description": "Present only when `include_proxy_source=true`. Identifies the instrument that actually backed the cohort factor that day.\n"
          }
        }
      },
      "CohortSeries": {
        "type": "object",
        "properties": {
          "ticker": {
            "type": "string"
          },
          "level": {
            "type": "integer",
            "enum": [
              1,
              2
            ]
          },
          "parent": {
            "type": "string",
            "nullable": true
          },
          "points": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/CohortSeriesPoint"
            }
          },
          "proxied_fraction": {
            "type": "number",
            "description": "Share of the returned window whose factor came from a substitute instrument (`factor_source != 0`). Two sector cohorts are majority-proxied over the full panel, so a long-history chart that hides this is showing partly a different basket.\n"
          }
        }
      },
      "CohortSeriesResponse": {
        "type": "object",
        "description": "Cohort statistics over a date range, one series per cohort.",
        "properties": {
          "cohorts": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/CohortSeries"
            }
          },
          "range": {
            "type": "array",
            "nullable": true,
            "items": {
              "type": "string",
              "format": "date"
            },
            "description": "[first, last] date actually returned."
          },
          "variables": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "min_names": {
            "type": "integer"
          },
          "universe": {
            "type": "string"
          },
          "market_factor_etf": {
            "type": "string"
          },
          "store_build": {
            "type": "string",
            "nullable": true
          },
          "disclosures": {
            "$ref": "#/components/schemas/CohortDisclosures"
          },
          "data_source": {
            "type": "string",
            "example": "zarr"
          }
        }
      },
      "CohortRosterResponse": {
        "type": "object",
        "description": "Addressable cohorts, their parent links, the variable catalogue, and the interpretation notes. Call this before /cohorts to learn what may be requested and what the numbers mean.\n",
        "properties": {
          "cohorts": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "ticker": {
                  "type": "string"
                },
                "level": {
                  "type": "integer",
                  "enum": [
                    1,
                    2
                  ]
                },
                "parent": {
                  "type": "string",
                  "nullable": true
                },
                "valid_from": {
                  "type": "string",
                  "format": "date",
                  "nullable": true
                },
                "valid_to": {
                  "type": "string",
                  "format": "date",
                  "nullable": true,
                  "description": "null = still open."
                }
              }
            }
          },
          "variables": {
            "type": "object",
            "properties": {
              "distribution": {
                "type": "array",
                "items": {
                  "type": "string"
                }
              },
              "breadth": {
                "type": "array",
                "items": {
                  "type": "string"
                }
              },
              "factor": {
                "type": "array",
                "items": {
                  "type": "string"
                }
              }
            }
          },
          "disclosures": {
            "$ref": "#/components/schemas/CohortDisclosures"
          },
          "universe": {
            "type": "string"
          },
          "panel": {
            "type": "array",
            "nullable": true,
            "items": {
              "type": "string",
              "format": "date"
            },
            "description": "[first, last] date in the store."
          }
        }
      },
      "CohortPnlDecompositionResponse": {
        "type": "object",
        "description": "Realized residual return split into within-cohort selection and net-exposure drift. `totals.selection + totals.drift == totals.residual` by construction.\n",
        "properties": {
          "level": {
            "type": "string",
            "enum": [
              "market",
              "sector"
            ]
          },
          "basis": {
            "type": "string",
            "description": "Which stock residual was used and which cohort mean it was demeaned against."
          },
          "range": {
            "type": "array",
            "nullable": true,
            "items": {
              "type": "string",
              "format": "date"
            }
          },
          "n_days": {
            "type": "integer"
          },
          "coverage": {
            "type": "object",
            "description": "What was actually included. Positions that could not be resolved or mapped to an addressable cohort are named here, never silently omitted.\n",
            "properties": {
              "requested": {
                "type": "integer"
              },
              "included": {
                "type": "integer"
              },
              "dropped": {
                "type": "array",
                "items": {
                  "type": "object",
                  "properties": {
                    "ticker": {
                      "type": "string"
                    },
                    "reason": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          },
          "totals": {
            "type": "object",
            "properties": {
              "residual": {
                "type": "number",
                "description": "Cumulative residual return of the book over the window."
              },
              "selection": {
                "type": "number",
                "description": "Part earned by holding names that beat their cohort average."
              },
              "drift": {
                "type": "number",
                "description": "Part earned from net exposure to the cohort average, independent of selection skill.\n"
              },
              "selection_share": {
                "type": "number",
                "nullable": true,
                "description": "|selection| / (|selection| + |drift|). Null when both are ~0.\n"
              }
            }
          },
          "by_cohort": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "cohort": {
                  "type": "string"
                },
                "net_weight": {
                  "type": "number"
                },
                "n_positions": {
                  "type": "integer"
                },
                "drift": {
                  "type": "number"
                },
                "selection": {
                  "type": "number"
                }
              }
            }
          },
          "series": {
            "type": "array",
            "description": "Daily contributions. Present only when include_series=true.",
            "items": {
              "type": "object",
              "properties": {
                "date": {
                  "type": "string",
                  "format": "date"
                },
                "selection": {
                  "type": "number"
                },
                "drift": {
                  "type": "number"
                },
                "residual": {
                  "type": "number"
                }
              }
            }
          },
          "net_weight": {
            "type": "number"
          },
          "gross_weight": {
            "type": "number"
          },
          "store_build": {
            "type": "string",
            "nullable": true
          },
          "disclosures": {
            "type": "object",
            "properties": {
              "interpretation": {
                "type": "string"
              },
              "no_intercept_contract": {
                "type": "string",
                "nullable": true
              },
              "constant_weights": {
                "type": "string"
              },
              "not_advice": {
                "type": "string"
              }
            }
          }
        }
      },
      "ResidualSignalReading": {
        "type": "object",
        "description": "One residual mean-reversion reading. In /residual-signal/{ticker} this is both the latest `signal` object and each `history` item; history items additionally carry `date`.\n",
        "properties": {
          "date": {
            "type": "string",
            "format": "date",
            "description": "Trading day (present on history items; omitted on the latest `signal`)."
          },
          "residual_z_5d": {
            "type": "number",
            "nullable": true,
            "description": "5-day cumulative L3 residual return, z-scored by 60-day residual vol. Negative = oversold."
          },
          "signal_strength": {
            "type": "number",
            "nullable": true,
            "description": "Absolute value of residual_z_5d."
          },
          "decile_rank": {
            "type": "integer",
            "nullable": true,
            "description": "1-10 cross-sectional decile of residual_z_5d (1 = most oversold)."
          },
          "industry_percentile": {
            "type": "number",
            "nullable": true,
            "description": "0-1 percentile of residual_z_5d within the stock's EODHD industry cohort."
          },
          "residual_autocorr_5d": {
            "type": "number",
            "nullable": true,
            "description": "Trailing-252d lag-5 autocorrelation of the 5-day cumulative residual. Negative = mean-reverting."
          },
          "l3_subsector_er": {
            "type": "number",
            "nullable": true,
            "description": "Subsector variance-explained fraction — the signal-quality conditioning variable."
          },
          "signal_quality_quintile": {
            "type": "integer",
            "nullable": true,
            "description": "1-5 quintile of l3_subsector_er (5 = tight subsector tracker = cleanest reversion regime)."
          }
        }
      },
      "ResidualSignalRow": {
        "description": "A universe cross-section row — a ResidualSignalReading keyed by ticker.",
        "allOf": [
          {
            "$ref": "#/components/schemas/ResidualSignalReading"
          },
          {
            "type": "object",
            "required": [
              "ticker"
            ],
            "properties": {
              "ticker": {
                "type": "string",
                "description": "Stock ticker."
              }
            }
          }
        ]
      },
      "LstarResponse": {
        "type": "object",
        "description": "Per-(ticker, date) recommended hedge level (L1/L2/L3) with the chosen level's dispatched hedge ratios and total explained-return. Arrays are parallel by date index. Each L\\* is a standalone hedge solution (not stacked layers) — `market_hr`/`sector_hr`/`subsector_hr` come from the chosen level's solver and are `null` where the chosen level doesn't use that ETF (sector_hr is null when L\\*=L1; subsector_hr is null when L\\*∈{L1,L2}). `axis` is always `industry` — the style axis was removed in v4 (style is a diagnostic block; see `POST /v4/decompose`).\n",
        "required": [
          "ticker",
          "axis",
          "dates",
          "lstar",
          "market_hr",
          "sector_hr",
          "subsector_hr",
          "total_er",
          "residual_return",
          "l2_sector_er",
          "l3_subsector_er",
          "threshold_used",
          "market_factor_etf",
          "universe",
          "data_source"
        ],
        "properties": {
          "ticker": {
            "type": "string"
          },
          "axis": {
            "type": "string",
            "enum": [
              "industry"
            ],
            "default": "industry",
            "description": "Cascade axis — always `industry`. `style` was removed in v4 (style is served as a diagnostic block by `POST /v4/decompose`).\n"
          },
          "dates": {
            "type": "array",
            "items": {
              "type": "string",
              "format": "date"
            }
          },
          "lstar": {
            "type": "array",
            "description": "Recommended level per date. `null` where source ERs are unavailable.",
            "items": {
              "type": "string",
              "enum": [
                "L1",
                "L2",
                "L3"
              ],
              "nullable": true
            }
          },
          "market_hr": {
            "type": "array",
            "items": {
              "type": "number",
              "format": "float",
              "nullable": true
            }
          },
          "sector_hr": {
            "type": "array",
            "description": "Hedge ratio for the sector ETF. `null` when `lstar` = `L1`.",
            "items": {
              "type": "number",
              "format": "float",
              "nullable": true
            }
          },
          "subsector_hr": {
            "type": "array",
            "description": "Hedge ratio for the subsector ETF. `null` when `lstar` ∈ {`L1`, `L2`}.",
            "items": {
              "type": "number",
              "format": "float",
              "nullable": true
            }
          },
          "total_er": {
            "type": "array",
            "description": "Sum of explained-return components at the chosen level (market + sector + subsector, omitting residual).\n",
            "items": {
              "type": "number",
              "format": "float",
              "nullable": true
            }
          },
          "residual_return": {
            "type": "array",
            "description": "Daily simple residual return at the chosen Lstar level (`l1_rr`, `l2_rr`, or `l3_rr` from returns decomposition). Parallel to `dates`; `null` when Lstar or source return is unavailable. Not a hedge ratio and not explained-risk variance (`l*_res_er`).\n",
            "items": {
              "type": "number",
              "format": "float",
              "nullable": true
            }
          },
          "l2_sector_er": {
            "type": "array",
            "description": "Marginal L2 (sector) ER input. Exposed for audit / threshold override.\n",
            "items": {
              "type": "number",
              "format": "float",
              "nullable": true
            }
          },
          "l3_subsector_er": {
            "type": "array",
            "description": "Marginal L3 (subsector) ER input. Exposed for audit / threshold override.\n",
            "items": {
              "type": "number",
              "format": "float",
              "nullable": true
            }
          },
          "threshold_used": {
            "type": "number",
            "format": "float",
            "example": 0.01,
            "description": "The marginal-ER threshold used to assign each date's level."
          },
          "market_factor_etf": {
            "type": "string"
          },
          "universe": {
            "type": "string"
          },
          "data_source": {
            "type": "string",
            "example": "zarr"
          }
        }
      },
      "BatchLstarTickerResult": {
        "type": "object",
        "description": "One entry in `POST /batch/lstar` `results` map (keyed by upper-case ticker).",
        "required": [
          "ticker",
          "status"
        ],
        "properties": {
          "ticker": {
            "type": "string"
          },
          "status": {
            "type": "string",
            "enum": [
              "success",
              "error",
              "not_found"
            ]
          },
          "error": {
            "type": "string",
            "description": "Present when status is `error`."
          },
          "dates": {
            "type": "array",
            "items": {
              "type": "string",
              "format": "date"
            }
          },
          "lstar": {
            "type": "array",
            "items": {
              "type": "string",
              "enum": [
                "L1",
                "L2",
                "L3"
              ],
              "nullable": true
            }
          },
          "market_hr": {
            "type": "array",
            "items": {
              "type": "number",
              "nullable": true
            }
          },
          "sector_hr": {
            "type": "array",
            "items": {
              "type": "number",
              "nullable": true
            }
          },
          "subsector_hr": {
            "type": "array",
            "items": {
              "type": "number",
              "nullable": true
            }
          },
          "total_er": {
            "type": "array",
            "items": {
              "type": "number",
              "nullable": true
            }
          },
          "residual_return": {
            "type": "array",
            "items": {
              "type": "number",
              "nullable": true
            }
          },
          "l2_sector_er": {
            "type": "array",
            "items": {
              "type": "number",
              "nullable": true
            }
          },
          "l3_subsector_er": {
            "type": "array",
            "items": {
              "type": "number",
              "nullable": true
            }
          },
          "threshold_used": {
            "type": "number"
          },
          "market_factor_etf": {
            "type": "string"
          },
          "universe": {
            "type": "string"
          },
          "data_source": {
            "type": "string"
          }
        }
      },
      "BatchLstarResponse": {
        "type": "object",
        "required": [
          "results",
          "summary",
          "years",
          "threshold_used",
          "market_factor_etf",
          "axis",
          "_agent"
        ],
        "properties": {
          "results": {
            "type": "object",
            "additionalProperties": {
              "$ref": "#/components/schemas/BatchLstarTickerResult"
            }
          },
          "summary": {
            "type": "object",
            "required": [
              "total",
              "success",
              "errors",
              "not_found"
            ],
            "properties": {
              "total": {
                "type": "integer"
              },
              "success": {
                "type": "integer"
              },
              "errors": {
                "type": "integer"
              },
              "not_found": {
                "type": "integer"
              }
            }
          },
          "years": {
            "type": "integer"
          },
          "threshold_used": {
            "type": "number"
          },
          "market_factor_etf": {
            "type": "string"
          },
          "axis": {
            "type": "string",
            "enum": [
              "industry"
            ],
            "description": "Always `industry` — the style axis was removed in v4."
          },
          "_agent": {
            "type": "object",
            "properties": {
              "cost_usd": {
                "type": "number"
              },
              "request_id": {
                "type": "string"
              }
            }
          },
          "_metadata": {
            "$ref": "#/components/schemas/RiskMetadata"
          }
        }
      },
      "FactorCorrelationRequest": {
        "type": "object",
        "required": [
          "ticker"
        ],
        "properties": {
          "ticker": {
            "oneOf": [
              {
                "type": "string",
                "example": "NVDA"
              },
              {
                "type": "array",
                "items": {
                  "type": "string"
                },
                "maxItems": 50
              }
            ]
          },
          "factors": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "Factor keys matching `macro_factors.factor_key`. Two sleeves share the table and endpoint; the `metadata.category` field on each row distinguishes them.\n**Macro sleeve (10 keys, from `ds_macro_factor.zarr`):** `inflation` (TIP), `term_spread` (VGIT), `short_rates` (BIL), `credit` (HYG), `oil` (USO), `gold` (GLD), `usd` (UUP), `volatility` (VXX short-term vol futures), `bitcoin` (BITO), `vix_spot` (FRED VIXCLS spot index). Note: `volatility` and `vix_spot` are DIFFERENT — VXX captures futures roll dynamics, VIXCLS is pure spot.\n**Style sleeve (8 keys, mirrored from `ds_etf.zarr`):** `momentum` (MTUM), `quality` (QUAL), `low_vol` (USMV), `value` (VLUE), `growth` (IWF), `size` (IWM), `dividend` (SCHD), `moat` (MOAT). History starts at each ETF's launch date (2011–2013 for MSCI factors; 2000 for IWF/IWM); pre-launch windows return `null` correlations. Style factors are most informative against `return_type=l3_residual`: because the residual is already orthogonal to SPY/sector/subsector, correlation isolates the pure style tilt.\nLegacy v1 aliases (`dxy`→`usd`, `vix`→`vix_spot`, `ust10y2y`→`term_spread`), common synonyms (btc, wti, xau, gld, cpi, tips), and style aliases (mtum, qual, usmv, vlue, iwf, iwm, schd, moat, minvol, small_cap) are normalized server-side. Defaults to all 18 canonical keys when omitted.\n"
          },
          "return_type": {
            "type": "string",
            "enum": [
              "gross",
              "l1",
              "l2",
              "l3_residual"
            ],
            "default": "l3_residual"
          },
          "window_days": {
            "type": "integer",
            "minimum": 20,
            "maximum": 2000,
            "default": 252
          },
          "method": {
            "type": "string",
            "enum": [
              "pearson",
              "spearman"
            ],
            "default": "pearson"
          }
        }
      },
      "FactorCorrelationSingleResponse": {
        "allOf": [
          {
            "$ref": "#/components/schemas/FactorCorrelationResultBody"
          },
          {
            "type": "object",
            "properties": {
              "_metadata": {
                "$ref": "#/components/schemas/RiskMetadata"
              },
              "_agent": {
                "type": "object",
                "properties": {
                  "latency_ms": {
                    "type": "integer"
                  },
                  "request_id": {
                    "type": "string"
                  }
                }
              }
            }
          }
        ]
      },
      "FactorCorrelationResponse": {
        "oneOf": [
          {
            "allOf": [
              {
                "$ref": "#/components/schemas/FactorCorrelationResultBody"
              },
              {
                "type": "object",
                "properties": {
                  "_metadata": {
                    "$ref": "#/components/schemas/RiskMetadata"
                  },
                  "_agent": {
                    "type": "object",
                    "properties": {
                      "latency_ms": {
                        "type": "integer"
                      },
                      "request_id": {
                        "type": "string"
                      }
                    }
                  }
                }
              }
            ]
          },
          {
            "type": "object",
            "required": [
              "results"
            ],
            "properties": {
              "results": {
                "type": "array",
                "items": {
                  "oneOf": [
                    {
                      "$ref": "#/components/schemas/FactorCorrelationResultBody"
                    },
                    {
                      "type": "object",
                      "required": [
                        "ticker",
                        "error",
                        "status"
                      ],
                      "properties": {
                        "ticker": {
                          "type": "string"
                        },
                        "error": {
                          "type": "string"
                        },
                        "status": {
                          "type": "integer"
                        }
                      }
                    }
                  ]
                }
              },
              "_metadata": {
                "$ref": "#/components/schemas/RiskMetadata"
              },
              "_agent": {
                "type": "object",
                "properties": {
                  "latency_ms": {
                    "type": "integer"
                  },
                  "request_id": {
                    "type": "string"
                  }
                }
              }
            }
          }
        ]
      },
      "FactorCorrelationResultBody": {
        "type": "object",
        "required": [
          "ticker",
          "return_type",
          "window_days",
          "method",
          "correlations",
          "overlap_days",
          "warnings"
        ],
        "properties": {
          "ticker": {
            "type": "string"
          },
          "return_type": {
            "type": "string",
            "enum": [
              "gross",
              "l1",
              "l2",
              "l3_residual"
            ]
          },
          "window_days": {
            "type": "integer"
          },
          "method": {
            "type": "string",
            "enum": [
              "pearson",
              "spearman"
            ]
          },
          "correlations": {
            "type": "object",
            "additionalProperties": {
              "type": "number",
              "nullable": true
            }
          },
          "overlap_days": {
            "type": "integer",
            "description": "Largest number of paired observations used among requested factors."
          },
          "warnings": {
            "type": "array",
            "items": {
              "type": "string"
            }
          }
        }
      },
      "MacroFactorsSeriesRow": {
        "type": "object",
        "required": [
          "factor_key",
          "teo"
        ],
        "properties": {
          "factor_key": {
            "type": "string",
            "example": "bitcoin"
          },
          "teo": {
            "type": "string",
            "format": "date"
          },
          "return_gross": {
            "type": "number",
            "format": "float",
            "nullable": true
          },
          "metadata": {
            "type": "object",
            "additionalProperties": true,
            "description": "Present only when non-empty in `macro_factors.metadata`."
          }
        }
      },
      "MacroFactorsSeriesResponse": {
        "type": "object",
        "required": [
          "factors_requested",
          "start",
          "end",
          "row_count",
          "series",
          "warnings"
        ],
        "properties": {
          "factors_requested": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "start": {
            "type": "string",
            "format": "date"
          },
          "end": {
            "type": "string",
            "format": "date"
          },
          "row_count": {
            "type": "integer"
          },
          "series": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/MacroFactorsSeriesRow"
            }
          },
          "warnings": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "_metadata": {
            "$ref": "#/components/schemas/RiskMetadata"
          },
          "_agent": {
            "type": "object",
            "properties": {
              "latency_ms": {
                "type": "integer"
              },
              "request_id": {
                "type": "string"
              }
            }
          }
        }
      },
      "SurfacePortfolioSeries": {
        "type": "object",
        "description": "L1/L2/L3 return-decomposition time series for an ETF / benchmark PortfolioSurface (same shape funds expose). The first key is the surface id (`ticker` for ETFs, `benchmark_context_id` for benchmarks).\n",
        "properties": {
          "portfolio_id": {
            "type": "string"
          },
          "ticker": {
            "type": "string"
          },
          "benchmark_context_id": {
            "type": "string"
          },
          "source_kind": {
            "type": "string",
            "enum": [
              "fund",
              "etf",
              "benchmark",
              "filer_13f"
            ]
          },
          "weight_basis": {
            "type": "string",
            "nullable": true,
            "enum": [
              "latest_holdings_constant",
              "time_varying"
            ],
            "description": "How the per-teo weights were derived. v1 = latest_holdings_constant."
          },
          "teo_frequency": {
            "type": "string",
            "example": "monthly"
          },
          "variance_shares": {
            "type": "object",
            "nullable": true,
            "description": "Diversification-credited full-window variance shares (≈ sum to 1).",
            "properties": {
              "market": {
                "type": "number",
                "nullable": true
              },
              "sector": {
                "type": "number",
                "nullable": true
              },
              "subsector": {
                "type": "number",
                "nullable": true
              },
              "residual": {
                "type": "number",
                "nullable": true
              }
            }
          },
          "n_rows": {
            "type": "integer"
          },
          "rows": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "teo": {
                  "type": "string",
                  "format": "date"
                },
                "portfolio_gross_return": {
                  "type": "number",
                  "nullable": true
                },
                "portfolio_market_return": {
                  "type": "number",
                  "nullable": true
                },
                "portfolio_sector_return": {
                  "type": "number",
                  "nullable": true
                },
                "portfolio_subsector_return": {
                  "type": "number",
                  "nullable": true
                },
                "portfolio_idiosyncratic_return": {
                  "type": "number",
                  "nullable": true
                },
                "identity_residual": {
                  "type": "number",
                  "nullable": true
                },
                "weight_sum": {
                  "type": "number",
                  "nullable": true
                },
                "n_holdings_active": {
                  "type": "integer",
                  "nullable": true
                },
                "effective_n": {
                  "type": "number",
                  "nullable": true
                },
                "top10_weight_sum": {
                  "type": "number",
                  "nullable": true
                }
              }
            }
          }
        }
      },
      "ErrorResponse": {
        "type": "object",
        "properties": {
          "error": {
            "type": "string",
            "description": "Machine-readable error code.",
            "example": "TICKER_NOT_FOUND"
          },
          "message": {
            "type": "string",
            "description": "Human-readable error description with suggested action.",
            "example": "Ticker 'XYZABC' not found in universe 'uni_mc_3000'. Check ticker spelling or try a different universe."
          },
          "code": {
            "type": "integer",
            "description": "HTTP status code.",
            "example": 404
          },
          "details": {
            "type": "object",
            "properties": {
              "field": {
                "type": "string",
                "example": "ticker"
              },
              "received": {
                "type": "string",
                "example": "XYZABC"
              },
              "universe": {
                "type": "string",
                "example": "uni_mc_3000"
              },
              "suggestion": {
                "type": "string",
                "example": "Check for typos or use ticker search endpoint"
              }
            }
          }
        }
      },
      "OAuthClientRegistrationRequest": {
        "type": "object",
        "required": [
          "redirect_uris"
        ],
        "properties": {
          "redirect_uris": {
            "type": "array",
            "minItems": 1,
            "items": {
              "type": "string",
              "format": "uri"
            },
            "description": "Absolute redirect URIs. `https://` and custom (native app) schemes are accepted; `http://` only for loopback hosts (`localhost`, `127.0.0.1`, `::1`).\n",
            "example": [
              "https://claude.ai/api/mcp/auth_callback"
            ]
          },
          "client_name": {
            "type": "string",
            "maxLength": 200,
            "example": "Claude Desktop"
          }
        },
        "additionalProperties": true
      },
      "OAuthClientRegistrationResponse": {
        "type": "object",
        "properties": {
          "client_id": {
            "type": "string",
            "format": "uuid",
            "example": "3f8a1c2e-5b47-4d90-9e21-7c6b0a4d8f13"
          },
          "client_id_issued_at": {
            "type": "integer",
            "description": "Unix timestamp (seconds)."
          },
          "redirect_uris": {
            "type": "array",
            "items": {
              "type": "string",
              "format": "uri"
            }
          },
          "grant_types": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "example": [
              "authorization_code",
              "refresh_token"
            ]
          },
          "response_types": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "example": [
              "code"
            ]
          },
          "token_endpoint_auth_method": {
            "type": "string",
            "enum": [
              "none"
            ],
            "description": "Public client — no client_secret is issued; PKCE binds the grant."
          },
          "client_name": {
            "type": "string",
            "description": "Present only if supplied at registration."
          }
        }
      },
      "OAuth2TokenRequest": {
        "type": "object",
        "required": [
          "grant_type"
        ],
        "description": "Shape depends on `grant_type`. For `authorization_code`: `code`, `redirect_uri`, `client_id`, `code_verifier` are all required. For `refresh_token`: `refresh_token` and `client_id` are required.\n",
        "properties": {
          "grant_type": {
            "type": "string",
            "enum": [
              "authorization_code",
              "refresh_token"
            ],
            "description": "`client_credentials` is not supported and returns `unsupported_grant_type`.\n"
          },
          "code": {
            "type": "string",
            "description": "Single-use authorization code (authorization_code grant)."
          },
          "redirect_uri": {
            "type": "string",
            "format": "uri",
            "description": "Must match the URI the code was issued for (authorization_code grant)."
          },
          "client_id": {
            "type": "string",
            "format": "uuid",
            "description": "Client id from `POST /api/oauth/register`."
          },
          "code_verifier": {
            "type": "string",
            "description": "PKCE verifier; must hash (S256) to the registered code_challenge."
          },
          "refresh_token": {
            "type": "string",
            "description": "Rotating refresh token (refresh_token grant)."
          }
        }
      },
      "OAuth2TokenResponse": {
        "type": "object",
        "properties": {
          "access_token": {
            "type": "string",
            "example": "rm_user_live_abc123_xyz789",
            "description": "An `rm_user_*` API key. Use directly as `Authorization: Bearer …` against any endpoint in this spec.\n"
          },
          "token_type": {
            "type": "string",
            "enum": [
              "Bearer"
            ]
          },
          "expires_in": {
            "type": "integer",
            "example": 3600,
            "description": "Access token lifetime in seconds (3600 = 1 hour)."
          },
          "refresh_token": {
            "type": "string",
            "description": "Valid 30 days and rotated on every use. Replaying a rotated token revokes the whole family for that user/client pair.\n"
          },
          "scope": {
            "type": "string",
            "example": "mcp:read"
          }
        }
      },
      "OAuth2RevokeRequest": {
        "type": "object",
        "required": [
          "token"
        ],
        "properties": {
          "token": {
            "type": "string",
            "description": "The `rm_user_*` access token or the opaque refresh token to revoke. The endpoint infers which from the `rm_user_` prefix.\n"
          }
        }
      },
      "OAuth2Error": {
        "type": "object",
        "properties": {
          "error": {
            "type": "string",
            "enum": [
              "invalid_request",
              "invalid_client_metadata",
              "invalid_redirect_uri",
              "invalid_grant",
              "unsupported_grant_type",
              "too_many_requests",
              "server_error"
            ]
          },
          "error_description": {
            "type": "string"
          }
        }
      },
      "PlaidHolding": {
        "type": "object",
        "properties": {
          "account_id": {
            "type": "string"
          },
          "security_id": {
            "type": "string"
          },
          "institution_value": {
            "type": "number",
            "description": "Current value in USD"
          },
          "quantity": {
            "type": "number"
          },
          "ticker": {
            "type": "string",
            "description": "Normalized ticker symbol"
          },
          "name": {
            "type": "string",
            "description": "Security name"
          },
          "risk_metrics": {
            "$ref": "#/components/schemas/RiskMetrics"
          }
        }
      },
      "RiskMetrics": {
        "type": "object",
        "description": "Optional risk enrichment on Plaid holdings; names may match batch `full_metrics` or V3 `metrics` keys depending on pipeline version.",
        "properties": {
          "annualized_volatility": {
            "type": "number",
            "nullable": true
          },
          "volatility": {
            "type": "number",
            "nullable": true
          },
          "sharpe_ratio": {
            "type": "number",
            "nullable": true
          },
          "l3_mkt_hr": {
            "type": "number",
            "nullable": true
          },
          "l3_sec_hr": {
            "type": "number",
            "nullable": true
          },
          "l3_sub_hr": {
            "type": "number",
            "nullable": true
          },
          "l3_res_er": {
            "type": "number",
            "nullable": true
          },
          "l3_market_hr": {
            "type": "number",
            "nullable": true
          },
          "l3_sector_hr": {
            "type": "number",
            "nullable": true
          },
          "l3_subsector_hr": {
            "type": "number",
            "nullable": true
          },
          "l3_residual_er": {
            "type": "number",
            "nullable": true
          }
        }
      },
      "MCPTool": {
        "type": "object",
        "description": "MCP tool descriptor as returned by `tools/list`. Tools are discoverable via `https://riskmodels.app/.well-known/mcp.json` or by connecting to the SSE endpoint at `https://riskmodels.app/api/mcp/sse` and calling `tools/list`.\n",
        "properties": {
          "name": {
            "type": "string",
            "enum": [
              "riskmodels_list_endpoints",
              "riskmodels_get_capability",
              "riskmodels_get_schema",
              "riskmodels_decompose",
              "riskmodels_compare",
              "riskmodels_get_hedge_levels",
              "riskmodels_analyze_portfolio",
              "riskmodels_hedge_portfolio",
              "get_l3_decomposition",
              "get_metrics",
              "get_portfolio_risk_snapshot",
              "post_snapshot"
            ],
            "description": "Representative MCP tool names for this deployment (hosted Streamable HTTP and stdio share the RiskModels subset;\nhosted adds live REST-backed tools).\n\nStdio/SDK-oriented tools (`registerRiskModelsTools`):\n- `riskmodels_decompose` — POST /decompose: L3 four-layer `exposure` + `hedge` map. For comparing L1/L2/L3\n  standalone solutions, prefer `riskmodels_get_hedge_levels` or read `hedge_levels` when present on decompose metrics.\n- `riskmodels_get_hedge_levels` — GET /metrics/{ticker} narrowed to the canonical `hedge_levels` block.\n- `riskmodels_compare` / `riskmodels_analyze_portfolio` — POST /batch/analyze for multiple tickers; portfolio path\n  renormalizes weights and surfaces aggregated `portfolio_hedge_levels` in the MCP wrapper output.\n- `riskmodels_hedge_portfolio` — Uses batch `/batch/analyze` per-ticker `hedge_levels` at the chosen cascade level and\n  scales to `notional_usd` positions (sums hedge notionals across ETFs).\n- `riskmodels_list_endpoints` / `riskmodels_get_capability` / `riskmodels_get_schema` — discovery.\n\nHosted-only tools (`mcp/src/server.ts`):\n- `get_l3_decomposition`, `get_metrics`, `get_portfolio_risk_snapshot`, `post_snapshot` — proxied REST with billing headers surfaced in the MCP JSON envelope.\n"
          },
          "description": {
            "type": "string"
          },
          "inputSchema": {
            "type": "object",
            "description": "JSON Schema describing the tool's input parameters."
          },
          "outputSchema": {
            "type": "object",
            "description": "JSON Schema describing the tool's output structure."
          }
        }
      },
      "RankingMetricKeys": {
        "type": "object",
        "description": "V3 security_history ranking metric keys (when rankings are exposed via API). Pattern: rank_ord_{window}_{cohort}_{metric}, cohort_size_{window}_{cohort}_{metric}. Windows: 1d, 21d, 63d, 252d. Cohorts: universe, sector, subsector. Metrics: mkt_cap, gross_return, sector_residual, subsector_residual, er_l1, er_l2, er_l3. rank_percentile computed client-side: (1 - (rank_ord - 1) / cohort_size) * 100.\n"
      },
      "InsufficientBalance": {
        "type": "object",
        "description": "Returned when the account prepaid balance is exhausted (HTTP 402).",
        "properties": {
          "error": {
            "type": "string",
            "enum": [
              "INSUFFICIENT_BALANCE"
            ],
            "example": "INSUFFICIENT_BALANCE"
          },
          "message": {
            "type": "string",
            "example": "Insufficient balance. Current balance: $0.00. Top up at https://riskmodels.app/billing"
          },
          "balance_usd": {
            "type": "number",
            "format": "float",
            "example": 0
          },
          "top_up_url": {
            "type": "string",
            "format": "uri",
            "example": "https://riskmodels.app/billing"
          }
        }
      },
      "RateLimitExceeded": {
        "type": "object",
        "description": "Returned when the per-minute rate limit is exceeded (HTTP 429).",
        "properties": {
          "error": {
            "type": "string",
            "enum": [
              "RATE_LIMIT_EXCEEDED"
            ],
            "example": "RATE_LIMIT_EXCEEDED"
          },
          "message": {
            "type": "string",
            "example": "Rate limit exceeded. Retry after 2026-03-08T12:34:56Z"
          },
          "retry_after": {
            "type": "string",
            "format": "date-time",
            "description": "ISO timestamp after which the client may retry.",
            "example": "2026-03-08T12:34:56Z"
          },
          "limit": {
            "type": "integer",
            "description": "Requests allowed per minute for this key.",
            "example": 60
          }
        }
      },
      "Fund": {
        "type": "object",
        "description": "Mutual fund registry row from `public.funds`. One row per `bw_fund_id`. Latest temporal triple (`latest_report_date` / `latest_filing_date` / `latest_extracted_at`) reflects the most recent N-PORT snapshot known for this fund. Q5 lock: `primary_bw_fund_id` is reserved as a nullable column; NULL means this row IS the primary share class (or no primary chosen yet). See ARCHITECTURE_FUNDS_API.md §4.1.\n",
        "properties": {
          "bw_fund_id": {
            "type": "string",
            "description": "Funds_DAG canonical fund id. Format `BW-FUND-{series_id}`.",
            "example": "BW-FUND-S000004310"
          },
          "series_id": {
            "type": "string",
            "nullable": true,
            "description": "SEC series id.",
            "example": "S000004310"
          },
          "ticker": {
            "type": "string",
            "nullable": true,
            "example": "VFINX"
          },
          "cik": {
            "type": "string",
            "nullable": true
          },
          "fund_name": {
            "type": "string",
            "nullable": true,
            "example": "Vanguard 500 Index Fund Investor Shares"
          },
          "morningstar_category": {
            "type": "string",
            "nullable": true
          },
          "equity_style_9box": {
            "type": "string",
            "nullable": true,
            "enum": [
              "Large Value",
              "Large Blend",
              "Large Growth",
              "Mid Value",
              "Mid Blend",
              "Mid Growth",
              "Small Value",
              "Small Blend",
              "Small Growth"
            ]
          },
          "style_link_method": {
            "type": "string",
            "nullable": true
          },
          "primary_bw_fund_id": {
            "type": "string",
            "nullable": true
          },
          "latest_report_date": {
            "type": "string",
            "format": "date",
            "nullable": true,
            "description": "Period end of the most recent reported holdings (= \"fund_date\")."
          },
          "latest_filing_date": {
            "type": "string",
            "format": "date",
            "nullable": true,
            "description": "SEC acceptance date for the latest filing (= \"release_date\")."
          },
          "latest_extracted_at": {
            "type": "string",
            "format": "date-time",
            "nullable": true
          },
          "latest_total_adj_mv": {
            "type": "number",
            "format": "float",
            "nullable": true
          },
          "latest_n_holdings": {
            "type": "integer",
            "nullable": true
          },
          "latest_effective_n": {
            "type": "number",
            "format": "float",
            "nullable": true
          },
          "last_in_eligible_universe_at": {
            "type": "string",
            "format": "date",
            "nullable": true
          },
          "status": {
            "type": "string",
            "nullable": true,
            "enum": [
              "active",
              "delisted"
            ],
            "description": "Fund lifecycle status. Null when not yet classified. Direct lookups return dead funds; list and search endpoints filter to active funds by default.\n"
          },
          "death_date": {
            "type": "string",
            "format": "date",
            "nullable": true,
            "description": "Date the fund ceased operations; null for live funds."
          },
          "is_etf": {
            "type": "boolean",
            "nullable": true
          },
          "is_etf_source": {
            "type": "string",
            "nullable": true
          },
          "is_money_market": {
            "type": "boolean",
            "nullable": true
          },
          "metadata": {
            "type": "object",
            "additionalProperties": true,
            "nullable": true
          }
        }
      },
      "FundLatest": {
        "type": "object",
        "description": "Wide-row latest knowledge-mode snapshot from `public.funds_latest`. One row per `bw_fund_id`. Returns and diagnostics flatten the per-fund `ds_portfolio.zarr` (Funds_DAG Slice 8). Multi-quarter history lives in GCS Zarr (Stage B+). See ARCHITECTURE_FUNDS_API.md §4.2.\n",
        "required": [
          "bw_fund_id",
          "report_date",
          "filing_date",
          "extracted_at",
          "last_synced_at"
        ],
        "properties": {
          "bw_fund_id": {
            "type": "string",
            "example": "BW-FUND-S000004310"
          },
          "report_date": {
            "type": "string",
            "format": "date",
            "example": "2026-04-30"
          },
          "filing_date": {
            "type": "string",
            "format": "date",
            "example": "2026-07-14"
          },
          "extracted_at": {
            "type": "string",
            "format": "date-time"
          },
          "portfolio_gross_return": {
            "type": "number",
            "format": "float",
            "nullable": true
          },
          "portfolio_market_return": {
            "type": "number",
            "format": "float",
            "nullable": true
          },
          "portfolio_sector_return": {
            "type": "number",
            "format": "float",
            "nullable": true
          },
          "portfolio_subsector_return": {
            "type": "number",
            "format": "float",
            "nullable": true
          },
          "portfolio_idiosyncratic_return": {
            "type": "number",
            "format": "float",
            "nullable": true
          },
          "identity_residual": {
            "type": "number",
            "format": "float",
            "nullable": true
          },
          "weight_sum": {
            "type": "number",
            "format": "float",
            "nullable": true,
            "description": "ERM3 universe coverage (fraction of fund AUM mapping to in-universe symbols)."
          },
          "n_holdings_active": {
            "type": "integer",
            "nullable": true
          },
          "effective_n": {
            "type": "number",
            "format": "float",
            "nullable": true,
            "description": "HHI-derived diversification (1 / sum(w_i^2))."
          },
          "top10_weight_sum": {
            "type": "number",
            "format": "float",
            "nullable": true
          },
          "total_adj_mv": {
            "type": "number",
            "format": "float",
            "nullable": true
          },
          "equity_style_9box": {
            "type": "string",
            "nullable": true
          },
          "n_funds_in_cell_at_report_date": {
            "type": "integer",
            "nullable": true
          },
          "model_version": {
            "type": "string",
            "nullable": true,
            "example": "funds_dag.v20260502"
          },
          "factor_set_id": {
            "type": "string",
            "nullable": true,
            "example": "uni_mc_3000_SPY"
          },
          "last_synced_at": {
            "type": "string",
            "format": "date-time"
          },
          "metadata": {
            "type": "object",
            "additionalProperties": true,
            "nullable": true
          }
        }
      },
      "FundWithLatest": {
        "type": "object",
        "description": "Fund registry row joined with the latest knowledge-mode snapshot.",
        "required": [
          "fund"
        ],
        "properties": {
          "fund": {
            "$ref": "#/components/schemas/Fund"
          },
          "latest": {
            "oneOf": [
              {
                "$ref": "#/components/schemas/FundLatest"
              },
              {
                "type": "null"
              }
            ],
            "nullable": true
          }
        }
      },
      "FundsBatchRequest": {
        "type": "object",
        "required": [
          "fund_ids"
        ],
        "properties": {
          "fund_ids": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "minItems": 1,
            "maxItems": 1000,
            "example": [
              "BW-FUND-S000004310",
              "BW-FUND-S000123456"
            ]
          }
        }
      },
      "FundsBatchResponse": {
        "type": "object",
        "description": "Multi-fund lookup. Keys are `bw_fund_id`s present in the registry.",
        "properties": {
          "results": {
            "type": "object",
            "additionalProperties": {
              "$ref": "#/components/schemas/FundWithLatest"
            }
          }
        }
      },
      "FundsSearchResponse": {
        "type": "object",
        "properties": {
          "results": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Fund"
            }
          }
        }
      },
      "FundsStyleMembersResponse": {
        "type": "object",
        "required": [
          "equity_style_9box",
          "slug",
          "fund_ids",
          "count"
        ],
        "properties": {
          "equity_style_9box": {
            "type": "string",
            "example": "Large Blend"
          },
          "slug": {
            "type": "string",
            "example": "large-blend"
          },
          "fund_ids": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "count": {
            "type": "integer"
          }
        }
      },
      "FundCohortRankEntry": {
        "type": "object",
        "description": "One ranking row for a fund within its 9-box cell. The \"n of n_group\" view: this fund placed `rank` out of `cohort_size` peers on this `metric` over `period_window`.\n",
        "required": [
          "metric",
          "period_window",
          "rank"
        ],
        "properties": {
          "metric": {
            "type": "string",
            "example": "portfolio_gross_return"
          },
          "period_window": {
            "type": "string",
            "enum": [
              "1m",
              "3m",
              "12m",
              "36m"
            ]
          },
          "rank": {
            "type": "integer",
            "example": 5
          },
          "cohort_size": {
            "type": "integer",
            "nullable": true,
            "example": 540
          },
          "value": {
            "type": "number",
            "format": "float",
            "nullable": true
          }
        }
      },
      "FundSnapshotResponse": {
        "type": "object",
        "description": "Composed JSON snapshot for a single mutual fund. Bundles registry + latest metrics + top-25 holdings + L1/L2/L3 hedge + 12-month portfolio time series + cohort context (rank-within-cell on every metric the rankings table covers).\n",
        "required": [
          "bw_fund_id",
          "report_date",
          "filing_date",
          "metrics",
          "portfolio_history",
          "_metadata"
        ],
        "properties": {
          "bw_fund_id": {
            "type": "string"
          },
          "ticker": {
            "type": "string",
            "nullable": true
          },
          "fund_name": {
            "type": "string",
            "nullable": true
          },
          "equity_style_9box": {
            "type": "string",
            "nullable": true
          },
          "report_date": {
            "type": "string",
            "format": "date"
          },
          "filing_date": {
            "type": "string",
            "format": "date"
          },
          "metrics": {
            "$ref": "#/components/schemas/FundMetricsResponse"
          },
          "holdings": {
            "type": "object",
            "nullable": true,
            "properties": {
              "n_total_holdings": {
                "type": "integer"
              },
              "n_returned": {
                "type": "integer"
              },
              "top": {
                "type": "array",
                "items": {
                  "$ref": "#/components/schemas/FundHolding"
                }
              }
            }
          },
          "hedge": {
            "oneOf": [
              {
                "$ref": "#/components/schemas/FundHedgeResponse"
              },
              {
                "type": "null"
              }
            ],
            "nullable": true
          },
          "portfolio_history": {
            "type": "object",
            "required": [
              "lookback_months",
              "n_periods",
              "rows"
            ],
            "properties": {
              "lookback_months": {
                "type": "integer",
                "example": 12
              },
              "n_periods": {
                "type": "integer"
              },
              "rows": {
                "type": "array",
                "items": {
                  "$ref": "#/components/schemas/FundPortfolioRow"
                }
              }
            }
          },
          "nav_history": {
            "type": "object",
            "nullable": true,
            "description": "Trailing 12-month yfinance NAV history. `null` when the fund has no yfinance-resolvable ticker (~35% of the universe — institutional SMAs, separately-managed accounts, mutual funds without retail tickers). Pair with `portfolio_history` on the same teos to surface the gap between 13F-derived attribution and realised NAV (intra-quarter trading, fees, cash drag).\n",
            "required": [
              "lookback_months",
              "n_periods",
              "rows"
            ],
            "properties": {
              "lookback_months": {
                "type": "integer",
                "example": 12
              },
              "n_periods": {
                "type": "integer"
              },
              "rows": {
                "type": "array",
                "items": {
                  "$ref": "#/components/schemas/FundNavRow"
                }
              }
            }
          },
          "cohort_context": {
            "type": "object",
            "nullable": true,
            "properties": {
              "equity_style_9box": {
                "type": "string",
                "nullable": true
              },
              "n_funds_in_cell": {
                "type": "integer",
                "nullable": true
              },
              "ranks": {
                "type": "array",
                "items": {
                  "$ref": "#/components/schemas/FundCohortRankEntry"
                }
              }
            }
          },
          "_metadata": {
            "type": "object",
            "properties": {
              "model_version": {
                "type": "string",
                "nullable": true
              },
              "factor_set_id": {
                "type": "string",
                "nullable": true
              },
              "data_as_of": {
                "type": "string",
                "format": "date"
              },
              "data_freshness": {
                "type": "string",
                "format": "date-time"
              }
            }
          }
        }
      },
      "CohortRankEntry": {
        "type": "object",
        "description": "One ranked entity inside a top_funds / top_symbols block.",
        "required": [
          "rank",
          "entity_id"
        ],
        "properties": {
          "rank": {
            "type": "integer"
          },
          "entity_id": {
            "type": "string"
          },
          "value": {
            "type": "number",
            "format": "float",
            "nullable": true
          },
          "cohort_size": {
            "type": "integer",
            "nullable": true
          }
        }
      },
      "CohortRankBlock": {
        "type": "object",
        "description": "A ranked list scoped to one (metric, period_window, weighting) tuple.",
        "required": [
          "metric",
          "period_window",
          "weighting",
          "rows"
        ],
        "properties": {
          "metric": {
            "type": "string"
          },
          "period_window": {
            "type": "string",
            "enum": [
              "1m",
              "3m",
              "12m",
              "36m"
            ]
          },
          "weighting": {
            "type": "string",
            "enum": [
              "ew",
              "mv"
            ]
          },
          "rows": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/CohortRankEntry"
            }
          }
        }
      },
      "CohortSnapshotResponse": {
        "type": "object",
        "description": "Composed JSON snapshot for a 9-box style cell — the differentiated wedge surface. Bundles cohort metrics (EW + MV), top-25 cohort holdings, 12-month cohort portfolio history, top-10 funds in cell, and top-15 symbols in cell.\n",
        "required": [
          "equity_style_9box",
          "slug",
          "report_date",
          "metrics",
          "portfolio_history",
          "_metadata"
        ],
        "properties": {
          "equity_style_9box": {
            "type": "string"
          },
          "slug": {
            "type": "string"
          },
          "report_date": {
            "type": "string",
            "format": "date"
          },
          "filing_date_max": {
            "type": "string",
            "format": "date",
            "nullable": true
          },
          "n_funds_in_cell": {
            "type": "integer",
            "nullable": true
          },
          "metrics": {
            "type": "object",
            "properties": {
              "weightings": {
                "type": "object",
                "properties": {
                  "ew": {
                    "$ref": "#/components/schemas/StyleCohortReturnsBlock"
                  },
                  "mv": {
                    "$ref": "#/components/schemas/StyleCohortReturnsBlock"
                  }
                }
              }
            }
          },
          "holdings": {
            "type": "object",
            "nullable": true,
            "properties": {
              "weighting": {
                "type": "string",
                "enum": [
                  "ew",
                  "mv"
                ]
              },
              "n_total_holdings": {
                "type": "integer"
              },
              "n_returned": {
                "type": "integer"
              },
              "top": {
                "type": "array",
                "items": {
                  "$ref": "#/components/schemas/CohortHolding"
                }
              }
            }
          },
          "portfolio_history": {
            "type": "object",
            "required": [
              "lookback_months",
              "n_periods",
              "rows"
            ],
            "properties": {
              "lookback_months": {
                "type": "integer",
                "example": 12
              },
              "n_periods": {
                "type": "integer"
              },
              "rows": {
                "type": "array",
                "items": {
                  "$ref": "#/components/schemas/CohortPortfolioRow"
                }
              }
            }
          },
          "top_funds": {
            "oneOf": [
              {
                "$ref": "#/components/schemas/CohortRankBlock"
              },
              {
                "type": "null"
              }
            ],
            "nullable": true
          },
          "top_symbols": {
            "oneOf": [
              {
                "$ref": "#/components/schemas/CohortRankBlock"
              },
              {
                "type": "null"
              }
            ],
            "nullable": true
          },
          "_metadata": {
            "type": "object",
            "properties": {
              "model_version": {
                "type": "string",
                "nullable": true
              },
              "data_as_of": {
                "type": "string",
                "format": "date"
              },
              "data_freshness": {
                "type": "string",
                "format": "date-time"
              }
            }
          }
        }
      },
      "CohortPortfolioRow": {
        "type": "object",
        "description": "One teo of a cohort portfolio time series. Both EW + MV blocks side-by-side.",
        "required": [
          "teo"
        ],
        "properties": {
          "teo": {
            "type": "string",
            "format": "date",
            "example": "2026-04-30"
          },
          "ew": {
            "$ref": "#/components/schemas/StyleCohortReturnsBlock"
          },
          "mv": {
            "$ref": "#/components/schemas/StyleCohortReturnsBlock"
          }
        }
      },
      "CohortPortfolioHistoryResponse": {
        "type": "object",
        "description": "Per-cell cohort portfolio time series from Slice 6's per-cell ds_portfolio.zarr. Each row carries both EW and MV blocks.\n",
        "required": [
          "equity_style_9box",
          "slug",
          "n_periods",
          "start_teo",
          "end_teo",
          "rows"
        ],
        "properties": {
          "equity_style_9box": {
            "type": "string"
          },
          "slug": {
            "type": "string"
          },
          "n_periods": {
            "type": "integer"
          },
          "start_teo": {
            "type": "string",
            "format": "date"
          },
          "end_teo": {
            "type": "string",
            "format": "date"
          },
          "rows": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/CohortPortfolioRow"
            }
          }
        }
      },
      "CohortHolding": {
        "type": "object",
        "description": "One cohort-aggregated holding within a 9-box cell.",
        "required": [
          "bw_sym_id",
          "weight"
        ],
        "properties": {
          "bw_sym_id": {
            "type": "string",
            "description": "Internal symbol id. Resolve via /api/data/symbols/batch."
          },
          "weight": {
            "type": "number",
            "format": "float",
            "description": "Cohort weight at this teo (sums to 1.0 within the chosen weighting)."
          },
          "contribution_gross": {
            "type": "number",
            "format": "float",
            "nullable": true
          },
          "contribution_market": {
            "type": "number",
            "format": "float",
            "nullable": true
          },
          "contribution_sector": {
            "type": "number",
            "format": "float",
            "nullable": true
          },
          "contribution_subsector": {
            "type": "number",
            "format": "float",
            "nullable": true
          },
          "contribution_idiosyncratic": {
            "type": "number",
            "format": "float",
            "nullable": true
          },
          "n_funds_holding": {
            "type": "integer",
            "nullable": true,
            "description": "Number of funds in the cell holding this symbol."
          }
        }
      },
      "CohortHoldingsResponse": {
        "type": "object",
        "description": "Top-N cohort holdings at the latest teo for the chosen weighting.",
        "required": [
          "equity_style_9box",
          "slug",
          "teo",
          "weighting",
          "n_returned",
          "n_total_holdings",
          "holdings"
        ],
        "properties": {
          "equity_style_9box": {
            "type": "string"
          },
          "slug": {
            "type": "string"
          },
          "teo": {
            "type": "string",
            "format": "date"
          },
          "weighting": {
            "type": "string",
            "enum": [
              "ew",
              "mv"
            ]
          },
          "n_returned": {
            "type": "integer"
          },
          "n_total_holdings": {
            "type": "integer"
          },
          "holdings": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/CohortHolding"
            }
          }
        }
      },
      "StyleCohortReturnsBlock": {
        "type": "object",
        "description": "Portfolio return components + diagnostics for one weighting in a cohort.",
        "properties": {
          "portfolio_gross_return": {
            "type": "number",
            "format": "float",
            "nullable": true
          },
          "portfolio_market_return": {
            "type": "number",
            "format": "float",
            "nullable": true
          },
          "portfolio_sector_return": {
            "type": "number",
            "format": "float",
            "nullable": true
          },
          "portfolio_subsector_return": {
            "type": "number",
            "format": "float",
            "nullable": true
          },
          "portfolio_idiosyncratic_return": {
            "type": "number",
            "format": "float",
            "nullable": true
          },
          "identity_residual": {
            "type": "number",
            "format": "float",
            "nullable": true
          },
          "weight_sum": {
            "type": "number",
            "format": "float",
            "nullable": true,
            "description": "Cohort coverage (fraction of cohort AUM in mapped symbols)."
          },
          "n_holdings_active": {
            "type": "integer",
            "nullable": true
          },
          "effective_n": {
            "type": "number",
            "format": "float",
            "nullable": true
          },
          "top10_weight_sum": {
            "type": "number",
            "format": "float",
            "nullable": true
          }
        }
      },
      "StyleCohortMetricsResponse": {
        "type": "object",
        "description": "Latest cohort metrics for a 9-box style cell. Both equal-weight (EW) and market-value-weighted (MV) cohort portfolios are returned side-by-side under `weightings`. Bitemporal lineage at top level.\n",
        "required": [
          "equity_style_9box",
          "slug",
          "report_date",
          "weightings",
          "_metadata"
        ],
        "properties": {
          "equity_style_9box": {
            "type": "string",
            "example": "Large Blend"
          },
          "slug": {
            "type": "string",
            "example": "large-blend"
          },
          "report_date": {
            "type": "string",
            "format": "date"
          },
          "filing_date_max": {
            "type": "string",
            "format": "date",
            "nullable": true
          },
          "n_funds_in_cell": {
            "type": "integer",
            "nullable": true
          },
          "weightings": {
            "type": "object",
            "properties": {
              "ew": {
                "$ref": "#/components/schemas/StyleCohortReturnsBlock"
              },
              "mv": {
                "$ref": "#/components/schemas/StyleCohortReturnsBlock"
              }
            }
          },
          "_metadata": {
            "type": "object",
            "properties": {
              "model_version": {
                "type": "string",
                "nullable": true
              },
              "data_as_of": {
                "type": "string",
                "format": "date"
              },
              "data_freshness": {
                "type": "string",
                "format": "date-time"
              },
              "last_synced_at": {
                "type": "string",
                "format": "date-time"
              }
            }
          }
        }
      },
      "StyleCohortRankingRow": {
        "type": "object",
        "required": [
          "rank",
          "entity_id"
        ],
        "properties": {
          "rank": {
            "type": "integer",
            "example": 1
          },
          "entity_id": {
            "type": "string",
            "description": "bw_sym_id (cohort=symbol), sector code (cohort=sector), or bw_fund_id (cohort=fund)."
          },
          "value": {
            "type": "number",
            "format": "float",
            "nullable": true
          },
          "cohort_size": {
            "type": "integer",
            "nullable": true
          }
        }
      },
      "StyleCohortRankingsResponse": {
        "type": "object",
        "required": [
          "equity_style_9box",
          "slug",
          "cohort_type",
          "metric",
          "period_window",
          "weighting",
          "report_date",
          "n_returned",
          "rows"
        ],
        "properties": {
          "equity_style_9box": {
            "type": "string"
          },
          "slug": {
            "type": "string"
          },
          "cohort_type": {
            "type": "string",
            "enum": [
              "symbol",
              "sector",
              "fund"
            ]
          },
          "metric": {
            "type": "string"
          },
          "period_window": {
            "type": "string",
            "enum": [
              "1m",
              "3m",
              "12m",
              "36m"
            ]
          },
          "weighting": {
            "type": "string",
            "enum": [
              "ew",
              "mv"
            ]
          },
          "report_date": {
            "type": "string",
            "format": "date"
          },
          "filing_date_max": {
            "type": "string",
            "format": "date",
            "nullable": true
          },
          "n_returned": {
            "type": "integer"
          },
          "rows": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/StyleCohortRankingRow"
            }
          }
        }
      },
      "FundHedgeLeg": {
        "type": "object",
        "description": "One ETF hedge leg at a given factor level.",
        "required": [
          "etf",
          "hr"
        ],
        "properties": {
          "etf": {
            "type": "string",
            "description": "ETF symbol (e.g. SPY, XLK, SMH)."
          },
          "hr": {
            "type": "number",
            "format": "float",
            "description": "Hedge ratio (dollar_ratio) — ETF notional per $1 of fund exposure at this level."
          }
        }
      },
      "FundHedgeResponse": {
        "type": "object",
        "description": "Latest L1/L2/L3 hedge ratios for a fund. Empty arrays are emitted for levels with no non-NaN entries (e.g. small / niche funds may only have an L1 market hedge). Composing a full hedge basket means concatenating L1 + L2 + L3 entries.\n",
        "required": [
          "bw_fund_id",
          "teo",
          "L1",
          "L2",
          "L3"
        ],
        "properties": {
          "bw_fund_id": {
            "type": "string"
          },
          "ticker": {
            "type": "string",
            "nullable": true
          },
          "fund_name": {
            "type": "string",
            "nullable": true
          },
          "equity_style_9box": {
            "type": "string",
            "nullable": true
          },
          "teo": {
            "type": "string",
            "format": "date"
          },
          "L1": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/FundHedgeLeg"
            }
          },
          "L2": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/FundHedgeLeg"
            }
          },
          "L3": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/FundHedgeLeg"
            }
          }
        }
      },
      "FundHolding": {
        "type": "object",
        "description": "One holding (security) within a fund's top-N snapshot.",
        "required": [
          "bw_sym_id",
          "adj_mv"
        ],
        "properties": {
          "bw_sym_id": {
            "type": "string",
            "description": "Internal symbol id. Resolve to ticker via /api/data/symbols/batch.",
            "example": "BW-BBG000B9XRY4"
          },
          "adj_mv": {
            "type": "number",
            "format": "float",
            "description": "Adjusted market value of this position at the snapshot teo."
          },
          "weight": {
            "type": "number",
            "format": "float",
            "nullable": true,
            "description": "Fraction of `aum_erm3`. Null when aum_erm3 is null/0."
          }
        }
      },
      "FundHoldingsResponse": {
        "type": "object",
        "description": "Top-N current holdings for a fund at the latest teo. Sourced from per-fund `ds_ph.zarr` (Slice 5). `aum_reported` is sum(adj_mv) BEFORE universe filter; `aum_erm3` is sum AFTER (ERM3-mapped symbols only — the denominator for `weight`).\n",
        "required": [
          "bw_fund_id",
          "teo",
          "n_holdings_returned",
          "n_total_holdings",
          "holdings"
        ],
        "properties": {
          "bw_fund_id": {
            "type": "string"
          },
          "ticker": {
            "type": "string",
            "nullable": true
          },
          "fund_name": {
            "type": "string",
            "nullable": true
          },
          "equity_style_9box": {
            "type": "string",
            "nullable": true
          },
          "teo": {
            "type": "string",
            "format": "date"
          },
          "aum_reported": {
            "type": "number",
            "format": "float",
            "nullable": true
          },
          "aum_erm3": {
            "type": "number",
            "format": "float",
            "nullable": true
          },
          "n_holdings_returned": {
            "type": "integer",
            "example": 25
          },
          "n_total_holdings": {
            "type": "integer",
            "example": 503
          },
          "holdings": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/FundHolding"
            }
          }
        }
      },
      "FundPortfolioRow": {
        "type": "object",
        "description": "One teo (month-end) of a fund's portfolio time series.",
        "required": [
          "teo"
        ],
        "properties": {
          "teo": {
            "type": "string",
            "format": "date",
            "example": "2026-04-30"
          },
          "portfolio_gross_return": {
            "type": "number",
            "format": "float",
            "nullable": true
          },
          "portfolio_market_return": {
            "type": "number",
            "format": "float",
            "nullable": true
          },
          "portfolio_sector_return": {
            "type": "number",
            "format": "float",
            "nullable": true
          },
          "portfolio_subsector_return": {
            "type": "number",
            "format": "float",
            "nullable": true
          },
          "portfolio_idiosyncratic_return": {
            "type": "number",
            "format": "float",
            "nullable": true
          },
          "identity_residual": {
            "type": "number",
            "format": "float",
            "nullable": true
          },
          "weight_sum": {
            "type": "number",
            "format": "float",
            "nullable": true
          },
          "n_holdings_active": {
            "type": "integer",
            "nullable": true
          },
          "effective_n": {
            "type": "number",
            "format": "float",
            "nullable": true
          },
          "top10_weight_sum": {
            "type": "number",
            "format": "float",
            "nullable": true
          }
        }
      },
      "FundPortfolioHistoryResponse": {
        "type": "object",
        "description": "Per-fund portfolio time series. Long-form rows indexed by teo (month-end). The `start_teo` / `end_teo` summary fields reflect the post-filter window, not the full panel.\n",
        "required": [
          "bw_fund_id",
          "n_periods",
          "start_teo",
          "end_teo",
          "rows"
        ],
        "properties": {
          "bw_fund_id": {
            "type": "string"
          },
          "ticker": {
            "type": "string",
            "nullable": true
          },
          "fund_name": {
            "type": "string",
            "nullable": true
          },
          "equity_style_9box": {
            "type": "string",
            "nullable": true
          },
          "n_periods": {
            "type": "integer",
            "example": 85
          },
          "start_teo": {
            "type": "string",
            "format": "date"
          },
          "end_teo": {
            "type": "string",
            "format": "date"
          },
          "rows": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/FundPortfolioRow"
            }
          }
        }
      },
      "FundNavRow": {
        "type": "object",
        "description": "One month-end NAV observation from yfinance. Pairs with `FundPortfolioRow` on the same teo: the portfolio row is 13F-derived attribution, the NAV row is what investors actually realised.\n",
        "required": [
          "teo"
        ],
        "properties": {
          "teo": {
            "type": "string",
            "format": "date",
            "example": "2026-04-30"
          },
          "nav_close": {
            "type": "number",
            "format": "float",
            "nullable": true,
            "description": "Month-end close price from yfinance."
          },
          "nav_return_monthly": {
            "type": "number",
            "format": "float",
            "nullable": true,
            "description": "pct_change of consecutive month-end closes (the realised return)."
          }
        }
      },
      "FundNavHistoryResponse": {
        "type": "object",
        "description": "Per-fund NAV time series. Same shape as the portfolio history response — rows indexed by teo (month-end), filtered to the post-`start_date` / pre-`end_date` window.\n",
        "required": [
          "bw_fund_id",
          "n_periods",
          "start_teo",
          "end_teo",
          "rows"
        ],
        "properties": {
          "bw_fund_id": {
            "type": "string"
          },
          "ticker": {
            "type": "string",
            "nullable": true
          },
          "fund_name": {
            "type": "string",
            "nullable": true
          },
          "equity_style_9box": {
            "type": "string",
            "nullable": true
          },
          "n_periods": {
            "type": "integer",
            "example": 196
          },
          "start_teo": {
            "type": "string",
            "format": "date"
          },
          "end_teo": {
            "type": "string",
            "format": "date"
          },
          "rows": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/FundNavRow"
            }
          }
        }
      },
      "FundMetricsResponse": {
        "type": "object",
        "description": "Latest knowledge-mode metrics for a single mutual fund. Joins `public.funds` (registry) with `public.funds_latest` (the wide-row snapshot) and groups return components / diagnostics / meta into nested objects. Bitemporal triple is at the top level; lineage in `_metadata`.\n",
        "required": [
          "bw_fund_id",
          "report_date",
          "filing_date",
          "extracted_at",
          "returns",
          "diagnostics",
          "meta",
          "_metadata"
        ],
        "properties": {
          "bw_fund_id": {
            "type": "string",
            "example": "BW-FUND-S000004310"
          },
          "ticker": {
            "type": "string",
            "nullable": true,
            "example": "VFINX"
          },
          "fund_name": {
            "type": "string",
            "nullable": true
          },
          "equity_style_9box": {
            "type": "string",
            "nullable": true,
            "enum": [
              "Large Value",
              "Large Blend",
              "Large Growth",
              "Mid Value",
              "Mid Blend",
              "Mid Growth",
              "Small Value",
              "Small Blend",
              "Small Growth"
            ]
          },
          "report_date": {
            "type": "string",
            "format": "date",
            "example": "2026-04-30"
          },
          "filing_date": {
            "type": "string",
            "format": "date",
            "example": "2026-07-14"
          },
          "extracted_at": {
            "type": "string",
            "format": "date-time"
          },
          "returns": {
            "type": "object",
            "description": "Portfolio-level return components from the per-fund Slice 8 zarr.",
            "properties": {
              "gross": {
                "type": "number",
                "format": "float",
                "nullable": true
              },
              "market": {
                "type": "number",
                "format": "float",
                "nullable": true
              },
              "sector": {
                "type": "number",
                "format": "float",
                "nullable": true
              },
              "subsector": {
                "type": "number",
                "format": "float",
                "nullable": true
              },
              "idiosyncratic": {
                "type": "number",
                "format": "float",
                "nullable": true
              },
              "identity_residual": {
                "type": "number",
                "format": "float",
                "nullable": true,
                "description": "L3 noise diagnostic (gross − sum of layer returns)."
              }
            }
          },
          "diagnostics": {
            "type": "object",
            "properties": {
              "weight_sum": {
                "type": "number",
                "format": "float",
                "nullable": true,
                "description": "ERM3 universe coverage (fraction of fund AUM in mapped symbols)."
              },
              "n_holdings_active": {
                "type": "integer",
                "nullable": true
              },
              "effective_n": {
                "type": "number",
                "format": "float",
                "nullable": true,
                "description": "HHI-derived diversification (1 / sum(w_i^2))."
              },
              "top10_weight_sum": {
                "type": "number",
                "format": "float",
                "nullable": true
              }
            }
          },
          "meta": {
            "type": "object",
            "properties": {
              "total_adj_mv": {
                "type": "number",
                "format": "float",
                "nullable": true
              },
              "n_funds_in_cell_at_report_date": {
                "type": "integer",
                "nullable": true
              },
              "morningstar_category": {
                "type": "string",
                "nullable": true
              },
              "primary_bw_fund_id": {
                "type": "string",
                "nullable": true
              },
              "status": {
                "type": "string",
                "enum": [
                  "active",
                  "delisted"
                ],
                "nullable": true
              },
              "death_date": {
                "type": "string",
                "format": "date",
                "nullable": true
              },
              "latest_report_date": {
                "type": "string",
                "format": "date",
                "nullable": true,
                "description": "Period end of the fund's last reported holdings."
              }
            }
          },
          "_metadata": {
            "type": "object",
            "properties": {
              "model_version": {
                "type": "string",
                "nullable": true,
                "example": "funds_dag.v20260502"
              },
              "factor_set_id": {
                "type": "string",
                "nullable": true,
                "example": "uni_mc_3000_SPY"
              },
              "data_as_of": {
                "type": "string",
                "format": "date"
              },
              "data_freshness": {
                "type": "string",
                "format": "date-time"
              },
              "last_synced_at": {
                "type": "string",
                "format": "date-time"
              }
            }
          }
        }
      }
    }
  },
  "security": [
    {
      "BearerAuth": []
    }
  ],
  "paths": {
    "/oauth/register": {
      "post": {
        "summary": "Register an OAuth client (Dynamic Client Registration)",
        "description": "RFC 7591 dynamic client registration. MCP clients self-register here after discovering the authorization server at `/.well-known/oauth-authorization-server`.\n\nPublic clients only: no `client_secret` is issued and `token_endpoint_auth_method` is `none` — the authorization code grant is bound to the client by PKCE instead. `redirect_uris` must be absolute; `http://` is accepted only for loopback hosts (RFC 8252).\n\nRate limited to 30 registrations per IP per hour.\n",
        "operationId": "registerOAuthClient",
        "tags": [
          "Authentication"
        ],
        "security": [],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/OAuthClientRegistrationRequest"
              },
              "example": {
                "client_name": "Claude Desktop",
                "redirect_uris": [
                  "https://claude.ai/api/mcp/auth_callback"
                ]
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Client registered",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OAuthClientRegistrationResponse"
                }
              }
            }
          },
          "400": {
            "description": "`invalid_client_metadata` (body is not JSON) or `invalid_redirect_uri` (`redirect_uris` missing, empty, non-absolute, or a non-loopback `http://` URI).\n",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OAuth2Error"
                }
              }
            }
          },
          "429": {
            "description": "Registration rate limit exceeded",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OAuth2Error"
                }
              }
            }
          },
          "500": {
            "description": "server_error — client record could not be persisted",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OAuth2Error"
                }
              }
            }
          }
        }
      }
    },
    "/oauth/token": {
      "post": {
        "summary": "Exchange an authorization code or refresh token for an access token",
        "description": "OAuth 2.0 token endpoint. Supports exactly two grants — `authorization_code` (PKCE S256, single-use codes) and `refresh_token` (rotating). There is **no** `client_credentials` grant; presenting one returns `unsupported_grant_type`.\n\nThe issued `access_token` is an `rm_user_*` API key valid for 1 hour, usable as `Authorization: Bearer …` against any endpoint in this spec. The `refresh_token` is valid for 30 days and rotates on every use — replaying an already-rotated refresh token revokes the whole token family for that user/client pair (RFC 6819 §5.2.2.3).\n\nAccepts either `application/x-www-form-urlencoded` or `application/json`. Responses are `Cache-Control: no-store`. Rate limited to 60 requests per IP per minute.\n",
        "operationId": "exchangeOAuthToken",
        "tags": [
          "Authentication"
        ],
        "security": [],
        "requestBody": {
          "required": true,
          "content": {
            "application/x-www-form-urlencoded": {
              "schema": {
                "$ref": "#/components/schemas/OAuth2TokenRequest"
              }
            },
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/OAuth2TokenRequest"
              },
              "example": {
                "grant_type": "authorization_code",
                "code": "9f2c1d…",
                "redirect_uri": "https://claude.ai/api/mcp/auth_callback",
                "client_id": "3f8a1c2e-5b47-4d90-9e21-7c6b0a4d8f13",
                "code_verifier": "dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Access token issued",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OAuth2TokenResponse"
                },
                "example": {
                  "access_token": "rm_user_live_abc123_xyz789",
                  "token_type": "Bearer",
                  "expires_in": 3600,
                  "refresh_token": "5f3a…",
                  "scope": "mcp:read"
                }
              }
            }
          },
          "400": {
            "description": "`invalid_request` (missing parameters), `invalid_grant` (code/refresh token not found, expired, already used, or PKCE/client mismatch), or `unsupported_grant_type`.\n",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OAuth2Error"
                }
              }
            }
          },
          "429": {
            "description": "Token rate limit exceeded",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OAuth2Error"
                }
              }
            }
          },
          "500": {
            "description": "server_error — token issuance failed",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OAuth2Error"
                }
              }
            }
          }
        }
      }
    },
    "/oauth/revoke": {
      "post": {
        "summary": "Revoke an access or refresh token",
        "description": "RFC 7009 token revocation. Revokes the presented `rm_user_*` access token or refresh token. Rate limited to 60 requests per IP per minute. Per RFC 7009 the endpoint returns 200 even for unknown tokens.\n",
        "operationId": "revokeOAuthToken",
        "tags": [
          "Authentication"
        ],
        "security": [],
        "requestBody": {
          "required": true,
          "content": {
            "application/x-www-form-urlencoded": {
              "schema": {
                "$ref": "#/components/schemas/OAuth2RevokeRequest"
              }
            },
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/OAuth2RevokeRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Token revoked (or was already invalid). Body is an empty object."
          },
          "400": {
            "description": "invalid_request — `token` parameter missing",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OAuth2Error"
                }
              }
            }
          },
          "429": {
            "description": "Revocation rate limit exceeded",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OAuth2Error"
                }
              }
            }
          }
        }
      }
    },
    "/mcp/sse": {
      "get": {
        "summary": "MCP Streamable HTTP Connection",
        "description": "Model Context Protocol endpoint using the Streamable HTTP transport (successor to the legacy SSE+companion-POST pattern). A single URL handles both `GET` (optional server→client event stream) and `POST` (client→server JSON-RPC 2.0 messages).\n\n**Discovery:** The MCP server manifest is published at `https://riskmodels.app/.well-known/mcp.json` for automated client discovery.\n\n**Connection:**\n- Transport: Streamable HTTP (stateless mode)\n- Authentication: `Authorization: Bearer <key>` (preferred) or\n  `?api_key=<key>` query param (for `EventSource`, which can't set\n  custom headers).\n\n- Tool-call billing: charged per-invocation on the underlying REST\n  endpoint (e.g. `get_metrics` bills the same as `GET /metrics/{ticker}`).\n\n- **POST requests:** send `Accept: application/json, text/event-stream` (required by some MCP clients;\n  omitting it can yield **406 Not Acceptable**).\n\n\n**Usage — mcp-remote proxy:**\n```json\n{\n  \"mcpServers\": {\n    \"riskmodels\": {\n      \"command\": \"npx\",\n      \"args\": [\"mcp-remote\", \"https://riskmodels.app/api/mcp/sse\"],\n      \"env\": { \"AUTHORIZATION\": \"Bearer rm_agent_live_...\" }\n    }\n  }\n}\n```\n",
        "operationId": "mcpServerSSE",
        "tags": [
          "MCP"
        ],
        "security": [
          {
            "BearerAuth": []
          },
          {
            "OAuth2AuthorizationCode": [
              "mcp:read"
            ]
          }
        ],
        "responses": {
          "200": {
            "description": "SSE connection established",
            "content": {
              "text/event-stream": {
                "schema": {
                  "type": "string",
                  "format": "event-stream"
                },
                "example": "data: {\"jsonrpc\":\"2.0\",\"method\":\"notifications/initialized\",\"params\":{...}}\n\ndata: {\"jsonrpc\":\"2.0\",\"method\":\"notifications/ping\",\"params\":{\"timestamp\":1234567890}}\n"
              }
            }
          },
          "401": {
            "description": "Authentication required",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "402": {
            "description": "Insufficient balance.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/InsufficientBalance"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded.",
            "headers": {
              "Retry-After": {
                "schema": {
                  "type": "integer"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/RateLimitExceeded"
                }
              }
            }
          }
        }
      },
      "post": {
        "summary": "MCP JSON-RPC Request",
        "description": "Send JSON-RPC 2.0 messages to the MCP server.\n\n**Available methods:**\n- `tools/list` - List available MCP tools\n- `tools/call` - Invoke an MCP tool\n- `resources/list` - List available resources\n- `resources/read` - Read a resource\n",
        "operationId": "mcpServerRPC",
        "tags": [
          "MCP"
        ],
        "security": [
          {
            "BearerAuth": []
          },
          {
            "OAuth2AuthorizationCode": [
              "mcp:read"
            ]
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "jsonrpc",
                  "method",
                  "id"
                ],
                "properties": {
                  "jsonrpc": {
                    "type": "string",
                    "enum": [
                      "2.0"
                    ]
                  },
                  "method": {
                    "type": "string",
                    "example": "tools/call"
                  },
                  "params": {
                    "type": "object"
                  },
                  "id": {
                    "oneOf": [
                      {
                        "type": "string"
                      },
                      {
                        "type": "integer"
                      }
                    ]
                  }
                }
              },
              "example": {
                "jsonrpc": "2.0",
                "method": "tools/call",
                "params": {
                  "name": "riskmodels_get_capability",
                  "arguments": {
                    "id": "ticker-returns"
                  }
                },
                "id": 1
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "JSON-RPC response",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "jsonrpc": {
                      "type": "string",
                      "enum": [
                        "2.0"
                      ]
                    },
                    "result": {
                      "type": "object"
                    },
                    "error": {
                      "type": "object",
                      "properties": {
                        "code": {
                          "type": "integer"
                        },
                        "message": {
                          "type": "string"
                        }
                      }
                    },
                    "id": {
                      "oneOf": [
                        {
                          "type": "string"
                        },
                        {
                          "type": "integer"
                        }
                      ]
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Authentication required",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "402": {
            "description": "Insufficient balance.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/InsufficientBalance"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded.",
            "headers": {
              "Retry-After": {
                "schema": {
                  "type": "integer"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/RateLimitExceeded"
                }
              }
            }
          }
        }
      }
    },
    "/.well-known/ai-plugin.json": {
      "get": {
        "summary": "OpenAI GPT Plugin Manifest",
        "description": "Plugin manifest for OpenAI GPT Store integration. Enables ChatGPT and other OpenAI models to discover and use the RiskModels API.\n**Discovery URL:** Served at the **site origin** (`https://riskmodels.app/.well-known/ai-plugin.json`), not under `/api`. Generated clients that combine the first `servers[0].url` with this path must strip `/api` first.\n",
        "operationId": "getAIPluginManifest",
        "servers": [
          {
            "url": "https://riskmodels.app",
            "description": "Site origin (discovery)"
          }
        ],
        "tags": [
          "Compliance",
          "Discovery"
        ],
        "security": [],
        "responses": {
          "200": {
            "description": "Plugin manifest",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "schema_version": {
                      "type": "string",
                      "example": "v1"
                    },
                    "name_for_human": {
                      "type": "string",
                      "example": "RiskModels"
                    },
                    "name_for_model": {
                      "type": "string",
                      "example": "riskmodels"
                    },
                    "description_for_human": {
                      "type": "string"
                    },
                    "description_for_model": {
                      "type": "string"
                    },
                    "auth": {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "service_http"
                          ]
                        },
                        "authorization_type": {
                          "type": "string",
                          "enum": [
                            "bearer"
                          ]
                        }
                      }
                    },
                    "api": {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "openapi"
                          ]
                        },
                        "url": {
                          "type": "string",
                          "format": "uri"
                        }
                      }
                    },
                    "logo_url": {
                      "type": "string",
                      "format": "uri"
                    },
                    "contact_email": {
                      "type": "string",
                      "format": "email"
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/.well-known/agentic-disclosure.json": {
      "get": {
        "summary": "Agentic Privacy Disclosure",
        "servers": [
          {
            "url": "https://riskmodels.app",
            "description": "Site origin (discovery)"
          }
        ],
        "description": "Comprehensive data handling and privacy disclosure for AI marketplace compliance. Documents encryption, retention, third-party sharing, and regulatory compliance.\n**Discovery URL:** Served at the **site origin** (`https://riskmodels.app/.well-known/agentic-disclosure.json`), not under `/api`.\n\n**Key sections:**\n- Data handling (AES-256-GCM, GCP KMS)\n- Cryptographic shredding for GDPR compliance\n- Third-party sharing policy (none)\n- AI model usage (no training on user data)\n- Compliance (GDPR, SOC2, PCI-DSS)\n",
        "operationId": "getAgenticDisclosure",
        "tags": [
          "Compliance",
          "Privacy"
        ],
        "security": [],
        "responses": {
          "200": {
            "description": "Privacy disclosure document",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "service": {
                      "type": "object"
                    },
                    "data_handling": {
                      "type": "object"
                    },
                    "compliance": {
                      "type": "object"
                    },
                    "security": {
                      "type": "object"
                    },
                    "transparency": {
                      "type": "object"
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/.well-known/mcp.json": {
      "get": {
        "summary": "MCP Server Manifest",
        "servers": [
          {
            "url": "https://riskmodels.app",
            "description": "Site origin (discovery)"
          }
        ],
        "description": "Model Context Protocol server connection manifest. Enables AI model clients to discover and connect to the RiskModels MCP server.\n**Discovery URL:** Served at the **site origin** (`https://riskmodels.app/.well-known/mcp.json`), not under `/api`.\n",
        "operationId": "getMCPManifest",
        "tags": [
          "MCP",
          "Discovery"
        ],
        "security": [],
        "responses": {
          "200": {
            "description": "MCP server manifest",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "mcpServers": {
                      "type": "object",
                      "additionalProperties": {
                        "type": "object",
                        "properties": {
                          "name": {
                            "type": "string"
                          },
                          "description": {
                            "type": "string"
                          },
                          "url": {
                            "type": "string",
                            "format": "uri"
                          },
                          "transport": {
                            "type": "string",
                            "enum": [
                              "sse",
                              "stdio"
                            ]
                          },
                          "auth": {
                            "type": "object"
                          },
                          "capabilities": {
                            "type": "object"
                          },
                          "tools": {
                            "type": "array",
                            "items": {
                              "$ref": "#/components/schemas/MCPTool"
                            }
                          }
                        }
                      }
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/plaid/link-token": {
      "post": {
        "summary": "Create Plaid Link token (Investments)",
        "description": "One-time `link_token` for Plaid Link in the browser. Requires an authenticated user. After Link succeeds, exchange the `public_token` via `POST /plaid/exchange-public-token`. **No per-request charge.**\n",
        "operationId": "createPlaidLinkToken",
        "tags": [
          "Plaid Integration"
        ],
        "x-pricing": {
          "capability_id": "plaid-link-token",
          "tier": "baseline",
          "model": "per_request",
          "cost_usd": 0,
          "currency": "USD",
          "billing_code": "plaid_link_token_v1"
        },
        "security": [
          {
            "BearerAuth": []
          },
          {
            "OAuth2AuthorizationCode": [
              "mcp:read"
            ]
          }
        ],
        "responses": {
          "200": {
            "description": "Link token for Plaid Link SDK.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "link_token": {
                      "type": "string"
                    },
                    "expiration": {
                      "type": "string",
                      "description": "RFC-3339 datetime when the link_token expires."
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "500": {
            "description": "Plaid or configuration error."
          }
        }
      }
    },
    "/plaid/exchange-public-token": {
      "post": {
        "summary": "Exchange Plaid public token for stored item",
        "description": "Exchanges the short-lived `public_token` from Plaid Link for an access token, encrypted and stored server-side. Repeat connections create/update one row per Plaid `item_id`. **No per-request charge.**\n",
        "operationId": "exchangePlaidPublicToken",
        "tags": [
          "Plaid Integration"
        ],
        "x-pricing": {
          "capability_id": "plaid-exchange-public-token",
          "tier": "baseline",
          "model": "per_request",
          "cost_usd": 0,
          "currency": "USD",
          "billing_code": "plaid_exchange_v1"
        },
        "security": [
          {
            "BearerAuth": []
          },
          {
            "OAuth2AuthorizationCode": [
              "mcp:read"
            ]
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "public_token"
                ],
                "properties": {
                  "public_token": {
                    "type": "string",
                    "description": "From Plaid Link onSuccess."
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Item stored.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "item_id": {
                      "type": "string"
                    },
                    "request_id": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Missing or invalid body."
          },
          "401": {
            "description": "Unauthorized."
          },
          "500": {
            "description": "Plaid or storage error."
          }
        }
      }
    },
    "/plaid/holdings": {
      "get": {
        "summary": "Fetch Plaid-synced portfolio holdings",
        "description": "Returns enriched holdings from connected Plaid accounts with risk metrics.\n\n**Features:**\n- Automatic ticker resolution and normalization\n- Risk enrichment with factor exposures\n- Real-time portfolio valuation\n- Multi-account aggregation\n\n**Authentication:** Required (API Key or OAuth2 with plaid:holdings scope)\n**Billing:** $0.10 per request\n**Rate Limit:** 60 requests/minute\n",
        "operationId": "getPlaidHoldings",
        "tags": [
          "Plaid Integration"
        ],
        "x-pricing": {
          "capability_id": "plaid-holdings",
          "tier": "premium",
          "model": "per_request",
          "cost_usd": 0.1,
          "currency": "USD",
          "billing_code": "plaid_holdings_v3"
        },
        "security": [
          {
            "BearerAuth": []
          },
          {
            "OAuth2AuthorizationCode": [
              "mcp:read"
            ]
          }
        ],
        "responses": {
          "200": {
            "description": "Enriched holdings with risk metrics",
            "headers": {
              "X-API-Cost-USD": {
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "integer"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "holdings": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/PlaidHolding"
                      }
                    },
                    "accounts": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "additionalProperties": true
                      },
                      "description": "Plaid account objects from Investments Holdings (per connected item)."
                    },
                    "securities": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "additionalProperties": true
                      },
                      "description": "Plaid security objects referenced by holdings."
                    },
                    "connections_count": {
                      "type": "integer",
                      "description": "Number of connected Plaid items for this user."
                    },
                    "summary": {
                      "type": "object",
                      "properties": {
                        "total_value": {
                          "type": "number"
                        },
                        "account_count": {
                          "type": "integer"
                        },
                        "position_count": {
                          "type": "integer"
                        }
                      }
                    },
                    "_metadata": {
                      "type": "object",
                      "additionalProperties": true
                    },
                    "_agent": {
                      "$ref": "#/components/schemas/AgentMeta"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Authentication required",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "402": {
            "description": "Insufficient balance.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/InsufficientBalance"
                }
              }
            }
          },
          "403": {
            "description": "API key missing `plaid:holdings` scope.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded.",
            "headers": {
              "Retry-After": {
                "schema": {
                  "type": "integer"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/RateLimitExceeded"
                }
              }
            }
          },
          "503": {
            "description": "Plaid or token encryption not configured."
          }
        }
      }
    },
    "/metrics/{ticker}": {
      "get": {
        "summary": "(internal/legacy) Latest risk metrics snapshot",
        "description": "**Per Snapshot Architecture v3, prefer `POST /snapshot` with `type: \"ticker\"` for new clients.** This endpoint remains callable as an internal building block.\nReturns the latest V3 daily metrics for the ticker. Numeric fields are under the `metrics` object using abbreviated keys (`l3_mkt_hr`, `l3_sec_hr`, `l3_sub_hr`, `l3_mkt_er`, `l3_res_er`, `vol_23d`, etc.). The observation date is `teo`. Optional `display` maps keys to human-readable labels.\n\n**Authentication:** Required (API Key or OAuth2)\n**Billing:** $0.005 per request (deducted from account balance)\n**Rate Limit:** 60 requests/minute (default)\n",
        "operationId": "getMetrics",
        "tags": [
          "Risk Metrics"
        ],
        "x-pricing": {
          "capability_id": "metrics",
          "tier": "baseline",
          "model": "per_request",
          "cost_usd": 0.005,
          "currency": "USD",
          "billing_code": "metrics_v4"
        },
        "security": [
          {
            "BearerAuth": []
          },
          {
            "OAuth2AuthorizationCode": [
              "mcp:read"
            ]
          }
        ],
        "parameters": [
          {
            "name": "ticker",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "Ticker symbol (case-insensitive).",
            "example": "NVDA"
          }
        ],
        "responses": {
          "200": {
            "description": "Latest metrics snapshot.",
            "headers": {
              "X-API-Cost-USD": {
                "schema": {
                  "type": "string"
                },
                "description": "Cost deducted for this request (e.g. \"0.001\")."
              },
              "X-Cache-Status": {
                "schema": {
                  "type": "string",
                  "enum": [
                    "HIT",
                    "MISS",
                    "BYPASS"
                  ]
                }
              },
              "X-Data-Freshness": {
                "schema": {
                  "type": "string",
                  "format": "date-time"
                }
              },
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "Request limit per minute"
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Remaining requests in current window"
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "integer"
                },
                "description": "Unix timestamp when limit resets"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/MetricsSnapshotResponse"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid Bearer token.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "402": {
            "description": "Insufficient balance.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/InsufficientBalance"
                }
              }
            }
          },
          "404": {
            "description": "Symbol not found or no metrics row (see error message).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded.",
            "headers": {
              "Retry-After": {
                "schema": {
                  "type": "integer"
                },
                "description": "Seconds to wait before retrying"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/RateLimitExceeded"
                }
              }
            }
          }
        }
      }
    },
    "/fundamentals/{ticker}": {
      "get": {
        "summary": "Point-in-time quarterly fundamentals (derived analytics + SEC-sourced raw)",
        "description": "Quarterly fundamentals for a single ticker, point-in-time filtered: a row is visible if and only if its `filed_date` is on or before `as_of` (never \"latest\"). `filed_date_source` is `exact` (vendor filing date) or `approx` (period_end + 45 days, the 10-Q deadline, when the vendor date is missing).\n\n**Derived analytics + per-cell raw SEC exposure.** Rows carry TTM profitability ratios (`roe_ttm`, `roa_ttm`, `fcf_margin`), capital-return ratios (`payout_ratio`, `retention_ratio`, `buyback_ratio`, `total_payout_ratio`, `sustainable_growth`), `leverage_ratio`, ERM3 cascade betas with provenance, the cost-of-capital layer (`cost_of_equity`, `cost_of_debt`, `wacc`, `economic_profit`), and the equity-bridge residual + inputs mask. `sec_facts` carries RAW line items for the cells whose serving value is SEC XBRL (public); vendor-sourced cells are not included there.\n\nNumeric line items are stored at ~7 significant figures (float32) — suitable for analysis, not cent-level reconciliation. `gross_margin` and `operating_margin` are currently always null (inputs not yet in the store). `market_cap` is a current snapshot, not point-in-time per quarter.\n\n**Disclosures (also returned in every response body):**\n- Realized historical data only. No forecasts, no analyst targets, no buy/sell\n  signals.\n\n- Coverage starts ~2009 for most filers; pre-2009 and small-cap or\n  recently-IPO'd names are thin. Missing fields are null.\n\n- `beta_market` is a short-half-life conditional market beta, not a textbook\n  long-run CAPM beta; for defensive names `cost_of_equity` can fall below the\n  risk-free rate. That is a property of the conditional beta, not an error.\n\n- WACC uses BOOK-value weights. Market-value weights are the textbook\n  convention; compute them yourself if you have market-cap access.\n\n- TTM sums flows over the trailing 4 reported quarters; stock quantities are\n  point-in-time (ROE denominator uses trailing-4-quarter average equity).\n  Ratios need 4 finite quarters or they are null.\n\n- `erp` (default 0.05) and `tax_rate` (default 0.21) are always\n  caller-supplied parameters, echoed in the response; no equity-risk-premium\n  opinion is stored.\n\n- `rf_rate` is the Treasury constant-maturity yield at the selected\n  `rf_tenor` (3m|1y|2y|5y|10y|30y, default 10y — the valuation convention),\n  sampled at the last observation on or before each quarter's period end.\n  A short tenor should be paired with a bill-basis ERP or cost of capital\n  is understated.\n\n\n**Authentication:** Required (API Key or OAuth2)\n**Billing:** $0.02 per request (deducted from account balance)\n**Rate Limit:** 60 requests/minute (default)\n\nPer-symbol, per-call only: no batch variant, no CSV/bulk export (JSON only).\n",
        "operationId": "getFundamentals",
        "tags": [
          "Fundamentals"
        ],
        "x-pricing": {
          "capability_id": "fundamentals",
          "tier": "baseline",
          "model": "per_request",
          "cost_usd": 0.02,
          "currency": "USD",
          "billing_code": "fundamentals_v2"
        },
        "security": [
          {
            "BearerAuth": []
          },
          {
            "OAuth2AuthorizationCode": [
              "mcp:read"
            ]
          }
        ],
        "parameters": [
          {
            "name": "ticker",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "Ticker symbol (case-insensitive).",
            "example": "AAPL"
          },
          {
            "name": "as_of",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "format": "date"
            },
            "description": "Point-in-time date. Rows are visible iff filed_date <= as_of. Default: today.\n",
            "example": "2026-03-31"
          },
          {
            "name": "periods",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 40,
              "default": 8
            },
            "description": "Number of quarterly rows returned (most recent last). Max 40."
          },
          {
            "name": "erp",
            "in": "query",
            "required": false,
            "schema": {
              "type": "number",
              "minimum": 0,
              "maximum": 0.5,
              "default": 0.05
            },
            "description": "Equity risk premium for the cost-of-capital layer (caller-supplied)."
          },
          {
            "name": "tax_rate",
            "in": "query",
            "required": false,
            "schema": {
              "type": "number",
              "minimum": 0,
              "maximum": 1,
              "default": 0.21
            },
            "description": "Tax rate applied to the WACC debt shield."
          },
          {
            "name": "rf_tenor",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "3m",
                "1y",
                "2y",
                "5y",
                "10y",
                "30y"
              ],
              "default": "10y"
            },
            "description": "Treasury constant-maturity tenor backing rf_rate. Default 10y (valuation convention). Pair a short tenor with a bill-basis ERP or cost of capital is understated."
          },
          {
            "name": "grid",
            "in": "query",
            "required": false,
            "schema": {
              "type": "boolean",
              "default": false
            },
            "description": "When true, the response also carries `sensitivity_grid` — `cost_of_equity` / `wacc` / `economic_profit` across `erp_grid` x `rf_tenor_grid` for the latest PIT-visible period only (not a time series — a \"what if my assumptions were different today\" table). Same billing as the base call; no extra cost."
          },
          {
            "name": "erp_grid",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            },
            "description": "Comma-separated ERP values for the sensitivity grid (1-10 values, each in [0, 0.5]), e.g. `0.03,0.04,0.05,0.06,0.07`. Ignored unless `grid=true`. Default: `0.03,0.04,0.05,0.06,0.07`.",
            "example": "0.03,0.04,0.05,0.06,0.07"
          },
          {
            "name": "rf_tenor_grid",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            },
            "description": "Comma-separated subset of `3m,1y,2y,5y,10y,30y` for the sensitivity grid. Ignored unless `grid=true`. Default: all six tenors.",
            "example": "1y,10y,30y"
          }
        ],
        "responses": {
          "200": {
            "description": "PIT quarterly fundamentals rows.",
            "headers": {
              "X-API-Cost-USD": {
                "schema": {
                  "type": "string"
                },
                "description": "Cost deducted for this request (e.g. \"0.005\")."
              },
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "Request limit per minute"
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Remaining requests in current window"
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "integer"
                },
                "description": "Unix timestamp when limit resets"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/FundamentalsResponse"
                }
              }
            }
          },
          "400": {
            "description": "Malformed ticker or query parameter.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid Bearer token.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "402": {
            "description": "Insufficient balance.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/InsufficientBalance"
                }
              }
            }
          },
          "404": {
            "description": "Ticker not present in the fundamentals panel.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded.",
            "headers": {
              "Retry-After": {
                "schema": {
                  "type": "integer"
                },
                "description": "Seconds to wait before retrying"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/RateLimitExceeded"
                }
              }
            }
          }
        }
      }
    },
    "/v4/decompose": {
      "post": {
        "summary": "Decompose a position into named blocks (market / industry / style / stock-specific)",
        "description": "The v4 product spine: `market + industry(sector, subsector) + style + stock_specific ≈ 1.0`.\n\nThe `industry` block is ETF-hedgeable (per-layer `hedge_notional_per_1_long`); the `style` block is a **diagnostic** — exposures plus incremental explained variance, never a tradeable hedge. `stock_specific` is the doubly-cleaned skill residual.\n\n`basis` selects the explained-variance basis for the `style` and `stock_specific` blocks: `L3` (default — the clean variance partition) or `lstar` (skill basis). Industry hedge layers are always L3.\n\nTwo leaf fields are `null` until their upstream producer lands and fill in with no schema change: `style.exposures.{size,value}.beta` and `stock_specific.{sharpe_36m,rank_percentile}`.\n\nThe legacy flat shape remains at `POST /decompose`, same billing profile.\n\n**Authentication:** Required (API Key or OAuth2)\n**Billing:** $0.005 per request\n**Rate Limit:** 120 requests/minute (default)\n",
        "operationId": "decomposePositionV4",
        "tags": [
          "Risk Metrics"
        ],
        "x-pricing": {
          "capability_id": "decompose-position",
          "tier": "baseline",
          "model": "per_request",
          "cost_usd": 0.005,
          "currency": "USD",
          "billing_code": "metrics_v4"
        },
        "security": [
          {
            "BearerAuth": []
          },
          {
            "OAuth2AuthorizationCode": [
              "mcp:read"
            ]
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "ticker"
                ],
                "properties": {
                  "ticker": {
                    "type": "string",
                    "example": "NVDA"
                  },
                  "basis": {
                    "type": "string",
                    "enum": [
                      "L3",
                      "lstar"
                    ],
                    "default": "L3"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Named-block decomposition",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "ticker": {
                      "type": "string"
                    },
                    "symbol": {
                      "type": "string"
                    },
                    "basis": {
                      "type": "string",
                      "enum": [
                        "L3",
                        "lstar"
                      ]
                    },
                    "data_as_of": {
                      "type": "string",
                      "format": "date"
                    },
                    "teo": {
                      "type": "string",
                      "format": "date"
                    },
                    "decomposition": {
                      "type": "object",
                      "properties": {
                        "market": {
                          "type": "object",
                          "properties": {
                            "factor": {
                              "type": "string",
                              "example": "SPY"
                            },
                            "beta": {
                              "type": "number",
                              "nullable": true
                            },
                            "explained_variance": {
                              "type": "number",
                              "nullable": true
                            },
                            "hedge_notional_per_1_long": {
                              "type": "number",
                              "nullable": true
                            },
                            "hedgeable": {
                              "type": "boolean",
                              "example": true
                            }
                          }
                        },
                        "industry": {
                          "type": "object",
                          "properties": {
                            "explained_variance": {
                              "type": "number",
                              "nullable": true
                            },
                            "hedgeable": {
                              "type": "boolean",
                              "example": true
                            },
                            "layers": {
                              "type": "object",
                              "properties": {
                                "sector": {
                                  "type": "object",
                                  "properties": {
                                    "etf": {
                                      "type": "string",
                                      "nullable": true
                                    },
                                    "beta": {
                                      "type": "number",
                                      "nullable": true
                                    },
                                    "explained_variance": {
                                      "type": "number",
                                      "nullable": true
                                    },
                                    "hedge_notional_per_1_long": {
                                      "type": "number",
                                      "nullable": true
                                    }
                                  }
                                },
                                "subsector": {
                                  "type": "object",
                                  "properties": {
                                    "etf": {
                                      "type": "string",
                                      "nullable": true
                                    },
                                    "beta": {
                                      "type": "number",
                                      "nullable": true
                                    },
                                    "explained_variance": {
                                      "type": "number",
                                      "nullable": true
                                    },
                                    "hedge_notional_per_1_long": {
                                      "type": "number",
                                      "nullable": true
                                    }
                                  }
                                }
                              }
                            }
                          }
                        },
                        "style": {
                          "type": "object",
                          "description": "Diagnostic only — not hedgeable.",
                          "properties": {
                            "explained_variance": {
                              "type": "number",
                              "nullable": true
                            },
                            "hedgeable": {
                              "type": "boolean",
                              "example": false
                            },
                            "exposures": {
                              "type": "object",
                              "description": "`size` is the IWM-IWB (SMB) spread; `value` is IWD-IWF (HML). `beta` is null until the ERM3 per-stock loadings land.\n"
                            }
                          }
                        },
                        "stock_specific": {
                          "type": "object",
                          "properties": {
                            "explained_variance": {
                              "type": "number",
                              "nullable": true
                            },
                            "sharpe_36m": {
                              "type": "number",
                              "nullable": true
                            },
                            "rank_percentile": {
                              "type": "number",
                              "nullable": true
                            }
                          }
                        }
                      }
                    },
                    "_data_health": {
                      "type": "object",
                      "properties": {
                        "er_populated": {
                          "type": "boolean"
                        },
                        "er_sum": {
                          "type": "number",
                          "nullable": true
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Invalid request body (ticker missing or malformed)"
          },
          "401": {
            "description": "Authentication required"
          },
          "402": {
            "description": "Insufficient balance"
          },
          "404": {
            "description": "Ticker not found in the universe"
          }
        }
      }
    },
    "/hedge-basket/{ticker}": {
      "get": {
        "summary": "Structured hedge basket with a recommended hedge level and decision trace",
        "description": "The chat-rendered hedge table: per-layer ETF legs with a net-market-β subtotal, plus a `decision_trace` explaining why `recommended_level` was chosen. Composes the latest-teo metric snapshot (HRs, ERs, betas) with link-betas read from `ds_erm3_link_betas_{market_factor_etf}.zarr`.\n\n`user_segment` drives the leverage cap (default `family_office`, 2.0×).\n\n**Authentication:** Required (API Key or OAuth2)\n**Billing:** $0.02 per request\n**Rate Limit:** 120 requests/minute (default)\n",
        "operationId": "getHedgeBasket",
        "tags": [
          "Risk Metrics"
        ],
        "x-pricing": {
          "capability_id": "hedge-basket",
          "tier": "premium",
          "model": "per_request",
          "cost_usd": 0.02,
          "currency": "USD",
          "billing_code": "hedge_basket_v2"
        },
        "security": [
          {
            "BearerAuth": []
          },
          {
            "OAuth2AuthorizationCode": [
              "mcp:read"
            ]
          }
        ],
        "parameters": [
          {
            "name": "ticker",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "example": "NVDA"
          },
          {
            "name": "user_segment",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "retail",
                "family_office",
                "ls_equity",
                "stat_arb"
              ],
              "default": "family_office"
            },
            "description": "Drives the leverage cap applied to the recommended level."
          },
          {
            "name": "market_factor_etf",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "default": "SPY"
            },
            "description": "Market factor ETF whose link-beta cube is read."
          }
        ],
        "responses": {
          "200": {
            "description": "Hedge basket",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "ticker": {
                      "type": "string"
                    },
                    "as_of": {
                      "type": "string",
                      "format": "date"
                    },
                    "inputs": {
                      "type": "object",
                      "description": "Betas, layer ETF tickers, and the `lambda_s_to_m` / `lambda_u_to_m` link betas the basket was built from.\n"
                    },
                    "basket": {
                      "type": "object",
                      "properties": {
                        "recommended_level": {
                          "type": "string"
                        },
                        "statistical_lstar": {
                          "type": "number",
                          "nullable": true
                        }
                      }
                    },
                    "decision_trace": {
                      "type": "array",
                      "items": {
                        "type": "string"
                      },
                      "description": "Ordered rationale for the recommended level."
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Invalid ticker or query parameter"
          },
          "401": {
            "description": "Authentication required"
          },
          "402": {
            "description": "Insufficient balance"
          },
          "404": {
            "description": "Ticker not found in the universe"
          }
        }
      }
    },
    "/batch/latest-metrics": {
      "get": {
        "summary": "Latest L3 hedge ratios and explained variances for up to 100 tickers",
        "description": "Lightweight batch read from `security_history_latest` — one row per requested ticker with the six L3 scalars. Cheaper and narrower than `POST /batch/analyze`; use that endpoint when you need returns panels or full metrics.\n\nTickers are de-duplicated and upper-cased; the list is truncated to the first 100. Tickers that resolve but have no latest row come back with `date` and all six metrics `null` rather than being omitted, so the response aligns positionally with what resolved.\n\n**Authentication:** Required (API Key or OAuth2)\n**Billing:** $0.005 per request\n**Rate Limit:** 120 requests/minute (default)\n",
        "operationId": "getBatchLatestMetrics",
        "tags": [
          "Risk Metrics"
        ],
        "x-pricing": {
          "capability_id": "metrics-snapshot",
          "tier": "baseline",
          "model": "per_request",
          "cost_usd": 0.005,
          "currency": "USD",
          "billing_code": "metrics_snapshot_v2"
        },
        "security": [
          {
            "BearerAuth": []
          },
          {
            "OAuth2AuthorizationCode": [
              "mcp:read"
            ]
          }
        ],
        "parameters": [
          {
            "name": "tickers",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string"
            },
            "example": "NVDA,AAPL,MSFT",
            "description": "Comma- or whitespace-separated. Max 100 after de-duplication."
          }
        ],
        "responses": {
          "200": {
            "description": "Latest metrics per ticker",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "ticker": {
                            "type": "string"
                          },
                          "date": {
                            "type": "string",
                            "format": "date",
                            "nullable": true
                          },
                          "l3_mkt_hr": {
                            "type": "number",
                            "nullable": true
                          },
                          "l3_sec_hr": {
                            "type": "number",
                            "nullable": true
                          },
                          "l3_sub_hr": {
                            "type": "number",
                            "nullable": true
                          },
                          "l3_mkt_er": {
                            "type": "number",
                            "nullable": true
                          },
                          "l3_sec_er": {
                            "type": "number",
                            "nullable": true
                          },
                          "l3_sub_er": {
                            "type": "number",
                            "nullable": true
                          }
                        }
                      }
                    },
                    "source": {
                      "type": "string",
                      "example": "security_history_latest"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "`tickers` missing or empty after parsing"
          },
          "401": {
            "description": "Authentication required"
          },
          "402": {
            "description": "Insufficient balance"
          },
          "404": {
            "description": "None of the supplied tickers resolved to the universe"
          }
        }
      }
    },
    "/funds/search": {
      "get": {
        "summary": "Search funds to resolve a bw_fund_id",
        "description": "Funds discovery. Resolves a `bw_fund_id` for downstream `/funds/{bw_fund_id}/*` calls, which are metered.\n\n`net_expense_ratio` is a **percent per year** (0.59 = 0.59%/yr, not 0.0059), and `net_expense_ratio_asof` can trail by years — treat it as a staleness signal, not a current-fee guarantee.\n\n**Authentication:** None — this endpoint is public. It is wrapped in `withBilling` with `skipBilling: true`, which bypasses key validation entirely, so no `Authorization` header is required or checked.\n**Billing:** Free\n**Rate Limit:** 60 requests/minute per IP (`FUND_SEARCH_IP_RPM`). `limit` is capped at 100 rows on this public route; the keyed `/api/data/funds/search` mirror allows more.\n",
        "operationId": "searchFunds",
        "tags": [
          "Funds"
        ],
        "x-pricing": {
          "capability_id": "fund-search",
          "tier": "baseline",
          "model": "per_request",
          "cost_usd": 0,
          "currency": "USD",
          "billing_code": "fund_search_v1"
        },
        "security": [],
        "parameters": [
          {
            "name": "q",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            },
            "description": "Full-text match on ticker or fund name (ilike)."
          },
          {
            "name": "equity_style_9box",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            },
            "example": "large-blend",
            "description": "Style slug (`large-blend`) or canonical name (`Large Blend`)."
          },
          {
            "name": "primary",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "true",
                "false"
              ]
            },
            "description": "`true` restricts results to share-class primaries."
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "default": 50,
              "minimum": 1,
              "maximum": 100
            }
          },
          {
            "name": "include_inactive",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "true",
                "false"
              ],
              "default": "false"
            },
            "description": "`true` also returns funds that are not active for listing. By default only funds with lifecycle status `active` whose last holdings report is within 400 days of the latest active report date are returned.\n"
          },
          {
            "name": "include_etfs",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "true",
                "false"
              ],
              "default": "true"
            },
            "description": "`false` drops funds flagged `is_etf`."
          }
        ],
        "responses": {
          "200": {
            "description": "Matching funds",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "results": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "bw_fund_id": {
                            "type": "string"
                          },
                          "series_id": {
                            "type": "string",
                            "nullable": true
                          },
                          "ticker": {
                            "type": "string",
                            "nullable": true
                          },
                          "cik": {
                            "type": "string",
                            "nullable": true
                          },
                          "fund_name": {
                            "type": "string",
                            "nullable": true
                          },
                          "morningstar_category": {
                            "type": "string",
                            "nullable": true
                          },
                          "equity_style_9box": {
                            "type": "string",
                            "nullable": true
                          },
                          "style_link_method": {
                            "type": "string",
                            "nullable": true
                          },
                          "net_expense_ratio": {
                            "type": "number",
                            "nullable": true,
                            "description": "Percent per year (0.59 = 0.59%/yr)."
                          },
                          "net_expense_ratio_asof": {
                            "type": "string",
                            "format": "date",
                            "nullable": true
                          },
                          "primary_bw_fund_id": {
                            "type": "string",
                            "nullable": true
                          },
                          "latest_report_date": {
                            "type": "string",
                            "format": "date",
                            "nullable": true
                          },
                          "latest_filing_date": {
                            "type": "string",
                            "format": "date",
                            "nullable": true
                          },
                          "status": {
                            "type": "string",
                            "enum": [
                              "active",
                              "delisted"
                            ],
                            "nullable": true
                          },
                          "death_date": {
                            "type": "string",
                            "format": "date",
                            "nullable": true
                          },
                          "is_etf": {
                            "type": "boolean",
                            "nullable": true
                          },
                          "is_money_market": {
                            "type": "boolean",
                            "nullable": true
                          }
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "429": {
            "description": "Per-IP rate limit exceeded"
          }
        }
      }
    },
    "/metrics/{ticker}/snapshot.png": {
      "get": {
        "summary": "Single-ticker risk snapshot as a PNG image",
        "description": "Returns **image bytes**, not JSON. The PNG twin of `GET /metrics/{ticker}/snapshot.pdf`, rendered from the same report data and served from a Redis cache on repeat requests.\n\n**Authentication:** Required (API Key or OAuth2)\n**Billing:** $1.25 per request (premium tier)\n**Rate Limit:** 120 requests/minute (default)\n",
        "operationId": "getTickerSnapshotPng",
        "tags": [
          "Risk Metrics"
        ],
        "x-pricing": {
          "capability_id": "portfolio-risk-snapshot",
          "tier": "premium",
          "model": "per_request",
          "cost_usd": 1.25,
          "currency": "USD",
          "billing_code": "risk_snapshot_pdf_v2"
        },
        "security": [
          {
            "BearerAuth": []
          },
          {
            "OAuth2AuthorizationCode": [
              "mcp:read"
            ]
          }
        ],
        "parameters": [
          {
            "name": "ticker",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "example": "NVDA"
          }
        ],
        "responses": {
          "200": {
            "description": "PNG image bytes",
            "content": {
              "image/png": {
                "schema": {
                  "type": "string",
                  "format": "binary"
                }
              }
            }
          },
          "400": {
            "description": "Invalid ticker"
          },
          "401": {
            "description": "Authentication required"
          },
          "402": {
            "description": "Insufficient balance"
          }
        }
      }
    },
    "/snapshot/{entity_kind}": {
      "get": {
        "summary": "Stock Deep Dive snapshot as PNG or PDF",
        "description": "Returns **image or PDF bytes**, not JSON. Served from precomputed GCS objects.\n\nDespite the parameter name, the path segment is a **ticker** — it is called `entity_kind` so the folder can nest `/snapshot/{entity_kind}/{id}/panels/{slug}` without a Next.js sibling-dynamic route conflict. Reserved kinds (`stock`, `fund`, …) are not tickers: requesting `/snapshot/stock` on its own returns 400 with the panel URL shape.\n\nFor panel drill-down prefer `GET /snapshot/stock/{ticker}/panels/{slug}?format=png` (`_full` for the whole sheet).\n\n**Authentication:** Required (API Key or OAuth2)\n**Billing:** $1.25 per request (premium tier)\n**Rate Limit:** 120 requests/minute (default)\n",
        "operationId": "getDeepDiveSnapshot",
        "tags": [
          "Risk Metrics"
        ],
        "x-pricing": {
          "capability_id": "portfolio-risk-snapshot",
          "tier": "premium",
          "model": "per_request",
          "cost_usd": 1.25,
          "currency": "USD",
          "billing_code": "risk_snapshot_pdf_v2"
        },
        "security": [
          {
            "BearerAuth": []
          },
          {
            "OAuth2AuthorizationCode": [
              "mcp:read"
            ]
          }
        ],
        "parameters": [
          {
            "name": "entity_kind",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "example": "NVDA",
            "description": "A ticker. Reserved kind names (`stock`, `fund`) return 400."
          },
          {
            "name": "format",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "png",
                "pdf"
              ],
              "default": "png"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Snapshot bytes in the requested format",
            "content": {
              "image/png": {
                "schema": {
                  "type": "string",
                  "format": "binary"
                }
              },
              "application/pdf": {
                "schema": {
                  "type": "string",
                  "format": "binary"
                }
              }
            }
          },
          "400": {
            "description": "Reserved kind supplied instead of a ticker, or unsupported format"
          },
          "401": {
            "description": "Authentication required"
          },
          "402": {
            "description": "Insufficient balance"
          }
        }
      }
    },
    "/decompose": {
      "post": {
        "summary": "(internal/legacy) Decompose a position into four additive bets with hedge ratios",
        "description": "**Per Snapshot Architecture v3, prefer `POST /snapshot` with `type: \"ticker\"` for new clients.** This endpoint remains callable as an internal building block.\nReturns the simplified four-layer ERM3 exposure for a single ticker: `market`, `sector`, `subsector`, and `residual`. Each tradable layer (market / sector / subsector) maps to a hedge ETF from `ticker_metadata`; `residual` is stock-specific and not tradable. The response also includes a top-level `hedge` map of ETF → dollar ratio (negative of the layer hedge ratio by convention, so a positive stock HR yields a negative ETF notional: short the ETF to offset long stock exposure).\n\nThe response also carries `style` and `stock_specific`: a diagnostic FF2 (SMB + HML) split of `exposure.residual` into a non-tradable style share and the doubly-cleaned stock-selection residual, on the `hedge_levels` basis. See `POST /v4/decompose` for the same split expressed as named blocks.\n\nSame billing profile as `GET /metrics/{ticker}` — this is a thin semantic wrapper that flattens the L3 metrics into an agent-friendly shape. For the full L1/L2/L3 snapshot (all six HR names), use `GET /metrics/{ticker}`.\n\n**Authentication:** Required (API Key or OAuth2)\n**Billing:** $0.005 per request\n**Rate Limit:** 120 requests/minute (default)\n",
        "operationId": "decomposePosition",
        "tags": [
          "Risk Metrics"
        ],
        "x-pricing": {
          "capability_id": "decompose-position",
          "tier": "baseline",
          "model": "per_request",
          "cost_usd": 0.005,
          "currency": "USD",
          "billing_code": "metrics_v4"
        },
        "security": [
          {
            "BearerAuth": []
          },
          {
            "OAuth2AuthorizationCode": [
              "mcp:read"
            ]
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/DecomposeRequest"
              },
              "examples": {
                "nvda": {
                  "summary": "Decompose NVDA",
                  "value": {
                    "ticker": "NVDA"
                  }
                },
                "aapl": {
                  "summary": "Decompose AAPL",
                  "value": {
                    "ticker": "AAPL"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Four-layer decomposition with hedge map.",
            "headers": {
              "X-API-Cost-USD": {
                "schema": {
                  "type": "string"
                },
                "description": "Cost deducted for this request (e.g. \"0.001\")."
              },
              "X-Data-Freshness": {
                "schema": {
                  "type": "string",
                  "format": "date-time"
                }
              },
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "integer"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/DecomposeResponse"
                }
              }
            }
          },
          "400": {
            "description": "Malformed ticker or invalid JSON body.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid Bearer token.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "402": {
            "description": "Insufficient balance.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/InsufficientBalance"
                }
              }
            }
          },
          "404": {
            "description": "Symbol not found or no metrics row.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded.",
            "headers": {
              "Retry-After": {
                "schema": {
                  "type": "integer"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/RateLimitExceeded"
                }
              }
            }
          }
        }
      }
    },
    "/ticker-returns": {
      "get": {
        "summary": "Daily returns time series with rolling hedge ratios (stocks and ETFs)",
        "description": "Returns a daily time series of gross returns (`returns_gross`) and, for stocks, V3 rolling hedge ratios and explained-risk fields per day: `l3_mkt_hr`, `l3_sec_hr`, `l3_sub_hr`, `l3_mkt_er`, `l3_sec_er`, `l3_sub_er`, `l3_res_er`. Up to 15 years per request. **ETFs are supported**: requests for ETF tickers (e.g. `SPY`, `XLK`) return `date`, `returns_gross`, and `price_close` sourced from `ds_etf.zarr`; L1/L2/L3 columns are `null` because ETFs are not factor-decomposed. The response includes `asset_type` (\"stock\" or \"etf\") so clients can branch cleanly. For the full L1/L2/L3 **snapshot** (all six HR names on the latest day), use `GET /metrics/{ticker}` (`metrics` object). Response does not include a top-level `_agent` block; billing is applied server-side. Cost: $0.02 for 1 year + $0.01 per extra year (up to 15).\n",
        "operationId": "getTickerReturns",
        "tags": [
          "Risk Metrics"
        ],
        "x-pricing": {
          "capability_id": "ticker-returns",
          "tier": "baseline",
          "model": "per_request",
          "cost_usd": 0.02,
          "cost_per_extra_year_usd": 0.01,
          "currency": "USD",
          "billing_code": "ticker_returns_v3"
        },
        "parameters": [
          {
            "name": "ticker",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string"
            },
            "example": "NVDA"
          },
          {
            "name": "years",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 15,
              "default": 1
            },
            "description": "Number of years of history to return."
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer"
            },
            "description": "Maximum number of rows to return."
          },
          {
            "name": "nocache",
            "in": "query",
            "required": false,
            "schema": {
              "type": "boolean"
            },
            "description": "Bypass cache (incurs cost even if identical request was recently made)."
          },
          {
            "$ref": "#/components/parameters/FormatQueryTabular"
          }
        ],
        "responses": {
          "200": {
            "description": "Time series of daily returns, `price_close`, and V3 rolling HR/ER fields. JSON wraps rows in `data` with `symbol`, `ticker`, `meta`, `_metadata`. Parquet/CSV are a single table: one row per trading day; columns match `#/components/schemas/TickerReturnsDailyRow` in a stable key order (same field names as JSON object keys in each `data` element).\n",
            "headers": {
              "ETag": {
                "schema": {
                  "type": "string"
                },
                "description": "Weak ETag (`W/\"...\"`). Clients may send `If-None-Match` to obtain `304 Not Modified` when the series is unchanged for this ticker, `years`, and format.\n"
              },
              "X-Risk-Model-Version": {
                "schema": {
                  "type": "string"
                }
              },
              "X-Data-As-Of": {
                "schema": {
                  "type": "string",
                  "format": "date"
                }
              },
              "X-Factor-Set-Id": {
                "schema": {
                  "type": "string"
                }
              },
              "X-Universe-Size": {
                "schema": {
                  "type": "integer"
                }
              }
            },
            "content": {
              "application/vnd.apache.parquet": {
                "schema": {
                  "type": "string",
                  "format": "binary"
                },
                "description": "Apache Parquet (one table). Row schema equals `#/components/schemas/TickerReturnsDailyRow`. No `_metadata` columns; use response headers for lineage.\n"
              },
              "text/csv": {
                "schema": {
                  "type": "string"
                },
                "description": "UTF-8 CSV: header row then one row per trading day; columns match the Parquet export.\n",
                "example": "date,returns_gross,price_close,l3_mkt_hr,l3_sec_hr,l3_sub_hr,l3_mkt_er,l3_sec_er,l3_sub_er,l3_res_er\n2025-03-20,0.0123,950.25,0.95,0.12,0.03,0.41,0.22,0.08,0.29\n"
              },
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/TickerReturnsResponseV3"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid Bearer token.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "402": {
            "description": "Insufficient balance.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/InsufficientBalance"
                }
              }
            }
          },
          "404": {
            "description": "Ticker not found.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded.",
            "headers": {
              "Retry-After": {
                "schema": {
                  "type": "integer"
                },
                "description": "Seconds to wait before retrying."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/RateLimitExceeded"
                }
              }
            }
          }
        }
      }
    },
    "/returns": {
      "get": {
        "summary": "Daily gross returns time series (single ticker) — REMOVED",
        "description": "DEPRECATED — returns **410 Gone** with JSON (not HTML). Use `GET /ticker-returns?ticker=...`. SDK `get_returns()` already forwards to `/ticker-returns`.\n",
        "deprecated": true,
        "operationId": "getReturns",
        "tags": [
          "Risk Metrics",
          "Deprecated"
        ],
        "security": [],
        "responses": {
          "410": {
            "description": "Route removed; use /ticker-returns.",
            "headers": {
              "Deprecation": {
                "schema": {
                  "type": "string"
                }
              },
              "Link": {
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "error",
                    "message",
                    "replacement"
                  ],
                  "properties": {
                    "error": {
                      "type": "string",
                      "example": "gone"
                    },
                    "message": {
                      "type": "string"
                    },
                    "replacement": {
                      "type": "string",
                      "format": "uri"
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/etf-returns": {
      "get": {
        "summary": "Daily gross returns time series (ETF) — REMOVED",
        "description": "DEPRECATED — returns **410 Gone** with JSON. Use `GET /ticker-returns?ticker=SPY` (or another ETF).\n",
        "deprecated": true,
        "operationId": "getEtfReturns",
        "tags": [
          "Risk Metrics",
          "Deprecated"
        ],
        "security": [],
        "responses": {
          "410": {
            "description": "Route removed; use /ticker-returns with an ETF ticker.",
            "headers": {
              "Deprecation": {
                "schema": {
                  "type": "string"
                }
              },
              "Link": {
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "error",
                    "message",
                    "replacement"
                  ],
                  "properties": {
                    "error": {
                      "type": "string",
                      "example": "gone"
                    },
                    "message": {
                      "type": "string"
                    },
                    "replacement": {
                      "type": "string",
                      "format": "uri"
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/returns-decomposition": {
      "get": {
        "summary": "Full returns decomposition (L1/L2/L3 + optional Lstar)",
        "description": "One-call daily return decomposition: gross return plus L1/L2/L3 factor,\ncombined-factor, and residual return series from ds_erm3_returns zarr.\nSet `include_lstar=true` or `dispatch=lstar` to append Lstar level and\nLstar-dispatched residual return per date (prefers materialized zarr\nlstar_rr/lstar_level when present; otherwise derives from marginal ERs).\n",
        "operationId": "getReturnsDecomposition",
        "tags": [
          "Risk Metrics"
        ],
        "x-pricing": {
          "capability_id": "returns-decomposition",
          "tier": "premium",
          "model": "per_request",
          "cost_usd": 0.04,
          "cost_per_extra_year_usd": 0.01,
          "currency": "USD",
          "billing_code": "returns_decomposition_v2"
        },
        "parameters": [
          {
            "name": "ticker",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string"
            },
            "example": "AAPL"
          },
          {
            "name": "market_factor_etf",
            "in": "query",
            "schema": {
              "type": "string",
              "default": "SPY"
            }
          },
          {
            "name": "years",
            "in": "query",
            "schema": {
              "type": "integer",
              "default": 1,
              "minimum": 1,
              "maximum": 10
            }
          },
          {
            "name": "include_lstar",
            "in": "query",
            "schema": {
              "type": "boolean",
              "default": false
            },
            "description": "Include Lstar level and lstar_residual_return arrays."
          },
          {
            "name": "dispatch",
            "in": "query",
            "schema": {
              "type": "string",
              "enum": [
                "lstar"
              ]
            },
            "description": "Sugar alias — `dispatch=lstar` sets include_lstar=true."
          },
          {
            "name": "threshold",
            "in": "query",
            "schema": {
              "type": "number",
              "format": "float",
              "default": 0.01,
              "minimum": 0,
              "maximum": 0.5
            },
            "description": "Marginal ER threshold when deriving Lstar (ignored when zarr has lstar_level)."
          }
        ],
        "responses": {
          "200": {
            "description": "Returns decomposition time series",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ReturnsDecompositionResponse"
                }
              }
            }
          },
          "400": {
            "description": "Invalid request"
          },
          "404": {
            "description": "Ticker not found"
          },
          "500": {
            "description": "Internal error"
          }
        }
      }
    },
    "/industry-panel": {
      "get": {
        "summary": "Industry peer β cross-section",
        "description": "Cross-section from `ds_erm3_industry` zarr: log-mcap-weighted typical factor β,\ncross-sectional β dispersion, peer counts, and cohort weight mass by EODHD industry\ncode and cascade level (`market`, `sector`, `subsector`). Default `by=level` is one\nrow per (industry, level). `by=fact` is one row per (industry, fact). Multi-fact\ncells are historical (last L3 day 2021-06-22); the latest teo is `n_facts=1`.\n`by=fact` returns 409 only on a leftover level-keyed vintage. Default observation\ndate is the latest `teo` in the store; pass `teo=YYYY-MM-DD` for a historical slice.\n",
        "operationId": "getIndustryPanel",
        "tags": [
          "Risk Metrics"
        ],
        "x-pricing": {
          "capability_id": "industry-panel",
          "tier": "premium",
          "model": "per_request",
          "cost_usd": 0.04,
          "currency": "USD",
          "billing_code": "industry_panel_v2"
        },
        "parameters": [
          {
            "name": "market_factor_etf",
            "in": "query",
            "schema": {
              "type": "string",
              "default": "SPY"
            }
          },
          {
            "name": "teo",
            "in": "query",
            "schema": {
              "type": "string",
              "format": "date"
            },
            "description": "Observation date (default latest teo in zarr)."
          },
          {
            "name": "date",
            "in": "query",
            "schema": {
              "type": "string",
              "format": "date"
            },
            "description": "Alias for `teo`."
          },
          {
            "name": "level",
            "in": "query",
            "schema": {
              "type": "string",
              "enum": [
                "market",
                "sector",
                "subsector"
              ]
            },
            "description": "Optional filter to one cascade level."
          },
          {
            "name": "min_peers",
            "in": "query",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 500
            },
            "description": "Minimum n_companies (default from zarr min_peers attr, usually 5). Applied per fact before any by=level collapse."
          },
          {
            "name": "by",
            "in": "query",
            "schema": {
              "type": "string",
              "enum": [
                "level",
                "fact"
              ],
              "default": "level"
            },
            "description": "Grouping. `level` (default) keeps one row per (industry_code, level). `fact` emits one row per (industry, fact) with a `fact` ticker field; 409 on a level-keyed vintage.\n"
          }
        ],
        "responses": {
          "200": {
            "description": "Industry panel cross-section",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/IndustryPanelResponse"
                }
              }
            }
          },
          "400": {
            "description": "Invalid request"
          },
          "404": {
            "description": "Industry panel unavailable"
          },
          "409": {
            "description": "by=fact requested against a level-keyed vintage"
          },
          "429": {
            "description": "Rate limit exceeded.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/RateLimitExceeded"
                }
              }
            }
          },
          "500": {
            "description": "Internal error"
          }
        }
      }
    },
    "/stocks/{ticker}/commentary-bundle": {
      "get": {
        "summary": "Stock commentary evidence bundle (one pull)",
        "description": "One request for everything a single-name risk commentary note needs:\nlatest L1/L2/L3 metrics + `hedge_levels`, a trailing return-record\nsummary (arithmetic contributions + drawdown), cohort standing on\ngross return, peer variance shares, and residual leadership for the\nname's sector/subsector cohort.\n\nReplaces the consumer's previous fan-out across `/metrics`,\n`/returns-decomposition`, `/rankings/{ticker}`,\n`/cohorts/variance-shares`, and `/cohorts/residual-leadership`.\n\n**Partial enrichment:** metrics are required (unknown ticker → 404).\nThin cohorts, short windows, or missing rankings null the affected\npiece and list a reason under `refusals` — the response stays 200 so\nthe risk note can still render. Return-record values are **sums** of\ndaily returns (additive); `gross_compound` is reported separately.\n",
        "operationId": "getStockCommentaryBundle",
        "tags": [
          "Risk Metrics"
        ],
        "x-pricing": {
          "capability_id": "cohorts",
          "tier": "premium",
          "model": "per_request",
          "cost_usd": 0.04,
          "currency": "USD"
        },
        "parameters": [
          {
            "name": "ticker",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "Stock ticker (e.g. NVDA)"
          },
          {
            "name": "window",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "default": "252d",
              "pattern": "^[0-9]+d$"
            },
            "description": "Trailing window for return record and residual rank (e.g. 252d)"
          }
        ],
        "responses": {
          "200": {
            "description": "Commentary evidence bundle",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ticker",
                    "symbol",
                    "teo",
                    "window",
                    "metrics",
                    "hedge_levels",
                    "meta",
                    "coverage",
                    "refusals"
                  ],
                  "properties": {
                    "ticker": {
                      "type": "string"
                    },
                    "symbol": {
                      "type": "string"
                    },
                    "teo": {
                      "type": "string"
                    },
                    "window": {
                      "type": "string"
                    },
                    "metrics": {
                      "type": "object",
                      "additionalProperties": true
                    },
                    "hedge_levels": {
                      "type": "object",
                      "additionalProperties": true
                    },
                    "meta": {
                      "type": "object",
                      "properties": {
                        "sector_etf": {
                          "type": "string",
                          "nullable": true
                        },
                        "subsector_etf": {
                          "type": "string",
                          "nullable": true
                        }
                      }
                    },
                    "return_record": {
                      "type": "object",
                      "nullable": true,
                      "additionalProperties": true
                    },
                    "standing": {
                      "type": "object",
                      "nullable": true,
                      "additionalProperties": true
                    },
                    "cohort_shares": {
                      "type": "object",
                      "nullable": true,
                      "additionalProperties": true
                    },
                    "residual_rank": {
                      "type": "object",
                      "nullable": true,
                      "additionalProperties": true
                    },
                    "coverage": {
                      "type": "object",
                      "properties": {
                        "metrics": {
                          "type": "boolean"
                        },
                        "return_record": {
                          "type": "boolean"
                        },
                        "standing": {
                          "type": "boolean"
                        },
                        "cohort_shares": {
                          "type": "boolean"
                        },
                        "residual_rank": {
                          "type": "boolean"
                        }
                      }
                    },
                    "refusals": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "piece": {
                            "type": "string"
                          },
                          "reason": {
                            "type": "string"
                          }
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Invalid ticker or window"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "description": "Ticker not found / no metrics"
          },
          "429": {
            "description": "Rate limit exceeded"
          }
        }
      }
    },
    "/cohorts": {
      "get": {
        "summary": "Cohort residual statistics (cross-section)",
        "description": "One-teo cross-section of cross-sectional residual statistics by cohort, from\n`ds_erm3_cohorts`. A cohort is the market (level 1) or a GICS sector (level 2).\n\n**Residuals are not zero-mean.** ERM3 regressions are fitted *without an\nintercept*, deliberately, so each stock's residual retains its alpha. The\nconsequence is that the cross-sectional mean residual is **not zero**. If you\nare building a relative-ranking signal, subtract `residual_mean` at the level\nyour residual is defined against — sector residuals demean within sector\ncohorts. Any drift figure must be quoted with its window: the sign is not\nstable across the sample.\n\n`residual_sd` is the cross-sectional dispersion within a cohort — a measure of\nhow much there is to select from. It is a conditioning and allocation input,\nnot an alpha source; it multiplies skill and cannot create it. Read it with\n`mean_pairwise_corr`, which separates idiosyncratic dispersion from common\nmovement.\n\nThin cohorts produce meaningless statistics — filter with `min_names`, and use\n`n_effective` rather than `n_names` for anything breadth-related.\n\nPublic scope is SPY plus the 11 GICS sector SPDRs. The subsector cohort slate\nis proprietary and is not addressable through this API.\n",
        "operationId": "getCohorts",
        "tags": [
          "Risk Metrics"
        ],
        "x-pricing": {
          "capability_id": "cohorts",
          "tier": "premium",
          "model": "per_request",
          "cost_usd": 0.04,
          "currency": "USD",
          "billing_code": "cohorts_v2"
        },
        "parameters": [
          {
            "name": "cohorts",
            "in": "query",
            "schema": {
              "type": "string"
            },
            "description": "Comma-separated cohort tickers (SPY, XLE, XLB, XLI, XLY, XLP, XLV, XLF, XLK, XLC, XLU, XLRE). Default: all public cohorts.\n"
          },
          {
            "name": "variables",
            "in": "query",
            "schema": {
              "type": "string"
            },
            "description": "Comma-separated variable names. Default: residual_mean, residual_sd, mean_pairwise_corr, n_names, n_effective.\n"
          },
          {
            "name": "teo",
            "in": "query",
            "schema": {
              "type": "string",
              "format": "date"
            },
            "description": "Observation date (default latest teo in the store)."
          },
          {
            "name": "date",
            "in": "query",
            "schema": {
              "type": "string",
              "format": "date"
            },
            "description": "Alias for `teo`."
          },
          {
            "name": "min_names",
            "in": "query",
            "schema": {
              "type": "integer",
              "minimum": 0,
              "maximum": 5000
            },
            "description": "Drop cohorts with fewer than this many members — their residual statistics are noise. Default 0.\n"
          }
        ],
        "responses": {
          "200": {
            "description": "Cohort cross-section",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CohortCrossSectionResponse"
                }
              }
            }
          },
          "400": {
            "description": "Invalid request, or a cohort outside the addressable set."
          },
          "404": {
            "description": "Cohort data unavailable"
          },
          "429": {
            "description": "Rate limit exceeded.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/RateLimitExceeded"
                }
              }
            }
          },
          "500": {
            "description": "Internal error"
          }
        }
      }
    },
    "/cohorts/series": {
      "get": {
        "summary": "Cohort residual statistics (time series)",
        "description": "Cohort statistics over a date range, one series per cohort. This is the\nendpoint to use for demeaning: request `residual_mean` for the level your\nresidual is defined against and subtract it. See `/cohorts` for why that is\nnecessary — ERM3 fits residuals without an intercept, so the cross-sectional\nmean is not zero.\n\nThe panel runs from 2000-01-03, but full factor richness begins around 2006;\nearlier history leans on proxy or synthetic backfill. Each cohort reports\n`proxied_fraction`, the share of returned days whose factor came from a\nsubstitute instrument. This matters for long windows: two sector cohorts are\nmajority-proxied over the full panel, so a chart that does not surface the\nsubstitution shows partly a different basket. Pass `include_proxy_source=true`\nto see which instrument backed each day.\n",
        "operationId": "getCohortSeries",
        "tags": [
          "Risk Metrics"
        ],
        "x-pricing": {
          "capability_id": "cohorts-series",
          "tier": "premium",
          "model": "per_request",
          "cost_usd": 0.15,
          "currency": "USD",
          "billing_code": "cohorts_series_v2"
        },
        "parameters": [
          {
            "name": "cohorts",
            "in": "query",
            "schema": {
              "type": "string"
            },
            "description": "Comma-separated cohort tickers. Default: all public cohorts."
          },
          {
            "name": "variables",
            "in": "query",
            "schema": {
              "type": "string"
            },
            "description": "Comma-separated variable names. Default: residual_mean, residual_sd, mean_pairwise_corr, n_names, n_effective.\n"
          },
          {
            "name": "start_date",
            "in": "query",
            "schema": {
              "type": "string",
              "format": "date"
            },
            "description": "Window start (default panel start)."
          },
          {
            "name": "end_date",
            "in": "query",
            "schema": {
              "type": "string",
              "format": "date"
            },
            "description": "Window end (default latest teo)."
          },
          {
            "name": "min_names",
            "in": "query",
            "schema": {
              "type": "integer",
              "minimum": 0,
              "maximum": 5000
            },
            "description": "Drop days where the cohort had fewer than this many members."
          },
          {
            "name": "include_proxy_source",
            "in": "query",
            "schema": {
              "type": "string",
              "enum": [
                "true",
                "false"
              ]
            },
            "description": "Include the per-day instrument backing the cohort factor."
          }
        ],
        "responses": {
          "200": {
            "description": "Cohort time series",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CohortSeriesResponse"
                }
              }
            }
          },
          "400": {
            "description": "Invalid request, or a cohort outside the addressable set."
          },
          "404": {
            "description": "Cohort data unavailable"
          },
          "429": {
            "description": "Rate limit exceeded.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/RateLimitExceeded"
                }
              }
            }
          },
          "500": {
            "description": "Internal error"
          }
        }
      }
    },
    "/cohorts/roster": {
      "get": {
        "summary": "Cohort roster and variable catalogue (discovery)",
        "description": "The addressable cohorts, their parent links, the variable catalogue, and the\ninterpretation notes that govern correct use — including the no-intercept\ncontract read directly from the store's own metadata. Free; call this before\n`/cohorts` to learn what may be requested and what the numbers mean.\n\nThis lists the public cohorts only. It is not a census of the underlying store.\n",
        "operationId": "getCohortRoster",
        "tags": [
          "Risk Metrics"
        ],
        "x-pricing": {
          "capability_id": "cohorts-roster",
          "tier": "baseline",
          "model": "per_request",
          "cost_usd": 0,
          "currency": "USD",
          "billing_code": "cohorts_roster_v1"
        },
        "responses": {
          "200": {
            "description": "Cohort roster",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CohortRosterResponse"
                }
              }
            }
          },
          "404": {
            "description": "Cohort data unavailable"
          },
          "429": {
            "description": "Rate limit exceeded.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/RateLimitExceeded"
                }
              }
            }
          },
          "500": {
            "description": "Internal error"
          }
        }
      }
    },
    "/cohorts/pnl-decomposition": {
      "post": {
        "summary": "Selection vs drift decomposition",
        "description": "Splits a book's realized residual return into two parts:\n\n- **Selection** — what the book earned by holding names that beat their cohort's\n  average residual.\n- **Drift** — what it earned purely from net exposure to that average, which\n  accrues on net weight regardless of any selection skill.\n\nThe two sum to the total exactly. This is an identity, not a fitted attribution:\n\n    R_t = Σ_i w_i·(ε_i,t − μ_c(i),t) + Σ_c W_c·μ_c,t\n\nwhere `w_i` is a position weight, `ε_i,t` its residual, `μ_c,t` its cohort's mean\nresidual, and `W_c` the net weight in cohort `c`.\n\nThis answers *\"was I paid for stock-picking, or for being net long the average\nstock?\"* — a question that is only answerable because ERM3 fits residuals without\nan intercept, leaving a non-zero cross-sectional mean that the cohort store\nexposes.\n\nLevels must match: `level=sector` (default) demeans each name's sector-level\nresidual against its sector cohort; `level=market` uses market-level residuals\nagainst the market cohort.\n\nWeights are treated as **constant** over the window and are **not normalized** —\nrescaling them would change the drift term, which is proportional to net weight.\nPositions that cannot be resolved or mapped to an addressable cohort are dropped\nand named in `coverage.dropped` rather than silently omitted.\n\nRealized historical attribution only — not a forecast, not a backtest of a\nstrategy, and not a recommendation regarding any security.\n",
        "operationId": "postCohortPnlDecomposition",
        "tags": [
          "Risk Metrics"
        ],
        "x-pricing": {
          "capability_id": "cohorts-pnl-decomposition",
          "tier": "premium",
          "model": "per_request",
          "cost_usd": 0.25,
          "currency": "USD",
          "billing_code": "cohorts_pnl_decomposition_v2"
        },
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "positions"
                ],
                "properties": {
                  "positions": {
                    "type": "array",
                    "minItems": 1,
                    "maxItems": 500,
                    "items": {
                      "type": "object",
                      "required": [
                        "ticker",
                        "weight"
                      ],
                      "properties": {
                        "ticker": {
                          "type": "string"
                        },
                        "weight": {
                          "type": "number",
                          "description": "May be negative for a short."
                        }
                      }
                    }
                  },
                  "level": {
                    "type": "string",
                    "enum": [
                      "market",
                      "sector"
                    ],
                    "default": "sector"
                  },
                  "start_date": {
                    "type": "string",
                    "format": "date"
                  },
                  "end_date": {
                    "type": "string",
                    "format": "date"
                  },
                  "min_names": {
                    "type": "integer",
                    "minimum": 0,
                    "maximum": 5000,
                    "description": "Ignore cohort means on days the cohort had fewer than this many members.\n"
                  },
                  "include_series": {
                    "type": "boolean",
                    "default": false,
                    "description": "Include the daily selection/drift series."
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Selection vs drift decomposition",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CohortPnlDecompositionResponse"
                }
              }
            }
          },
          "400": {
            "description": "Invalid request"
          },
          "404": {
            "description": "Cohort data unavailable"
          },
          "429": {
            "description": "Rate limit exceeded.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/RateLimitExceeded"
                }
              }
            }
          },
          "500": {
            "description": "Internal error"
          }
        }
      }
    },
    "/rankings/screen": {
      "post": {
        "summary": "Cross-sectional rankings screen",
        "description": "Full-universe rank screen from `ds_rankings` at one `teo` (default latest). Applies server-side `min_percentile`, `decile` (1 = best decile), and optional `sector_filter` (sector ETF on `symbols.sector_etf`), then returns up to 500 names sorted by ascending `rank_ordinal`. Same `rank_percentile` rule as GET /rankings/top (100 = best).\n",
        "operationId": "postRankingsScreen",
        "tags": [
          "Risk Metrics"
        ],
        "x-pricing": {
          "capability_id": "rankings-screen",
          "tier": "premium",
          "model": "per_request",
          "cost_usd": 0.25,
          "currency": "USD",
          "billing_code": "rankings_screen_v2"
        },
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "metric",
                  "cohort",
                  "window"
                ],
                "properties": {
                  "metric": {
                    "type": "string",
                    "enum": [
                      "mkt_cap",
                      "gross_return",
                      "sector_residual",
                      "subsector_residual",
                      "er_l1",
                      "er_l2",
                      "er_l3"
                    ]
                  },
                  "cohort": {
                    "type": "string",
                    "enum": [
                      "universe",
                      "sector",
                      "subsector"
                    ]
                  },
                  "window": {
                    "type": "string",
                    "enum": [
                      "1d",
                      "21d",
                      "63d",
                      "252d"
                    ]
                  },
                  "as_of": {
                    "type": "string",
                    "format": "date",
                    "description": "Observation date (default latest teo in zarr)."
                  },
                  "min_percentile": {
                    "type": "number",
                    "minimum": 0,
                    "maximum": 100
                  },
                  "decile": {
                    "type": "integer",
                    "minimum": 1,
                    "maximum": 10
                  },
                  "sector_filter": {
                    "type": "string",
                    "description": "Sector ETF ticker (e.g. XLK)."
                  },
                  "limit": {
                    "type": "integer",
                    "minimum": 1,
                    "maximum": 500,
                    "default": 100
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Filtered cross-section rows and counts.",
            "headers": {
              "X-API-Cost-USD": {
                "schema": {
                  "type": "string"
                }
              },
              "X-Data-Fetch-Latency-Ms": {
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "teo": {
                      "type": "string",
                      "format": "date"
                    },
                    "metric": {
                      "type": "string"
                    },
                    "cohort": {
                      "type": "string"
                    },
                    "window": {
                      "type": "string"
                    },
                    "limit": {
                      "type": "integer"
                    },
                    "min_percentile": {
                      "type": "number",
                      "nullable": true
                    },
                    "decile": {
                      "type": "integer",
                      "nullable": true
                    },
                    "sector_filter": {
                      "type": "string",
                      "nullable": true
                    },
                    "universe_size": {
                      "type": "integer"
                    },
                    "matched_count": {
                      "type": "integer"
                    },
                    "universe": {
                      "type": "string"
                    },
                    "data_source": {
                      "type": "string"
                    },
                    "rankings": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "symbol": {
                            "type": "string"
                          },
                          "ticker": {
                            "type": "string"
                          },
                          "rank_ordinal": {
                            "type": "integer"
                          },
                          "cohort_size": {
                            "type": "integer",
                            "nullable": true
                          },
                          "rank_percentile": {
                            "type": "number",
                            "nullable": true
                          }
                        }
                      }
                    },
                    "_metadata": {
                      "type": "object",
                      "additionalProperties": true
                    },
                    "_agent": {
                      "$ref": "#/components/schemas/AgentMeta"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Invalid request body.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "402": {
            "description": "Insufficient balance.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/InsufficientBalance"
                }
              }
            }
          },
          "404": {
            "description": "Rankings data unavailable."
          }
        }
      }
    },
    "/l3-decomposition": {
      "get": {
        "summary": "L3 factor decomposition time series",
        "description": "Returns columnar time series of L3 hedge ratios and explained-risk fractions (parallel arrays by trading date) from V3 `security_history`. Components: market, sector, subsector, residual. Includes `ticker`, `universe`, and `data_source`. This route does not append `_agent` to the JSON body in the current implementation; use headers / account balance for billing if applicable.\n**Billing:** $0.04 per request.\n",
        "operationId": "getL3Decomposition",
        "tags": [
          "Risk Metrics"
        ],
        "x-pricing": {
          "capability_id": "l3-decomposition",
          "tier": "premium",
          "model": "per_request",
          "cost_usd": 0.04,
          "currency": "USD",
          "billing_code": "l3_decomposition_v3"
        },
        "parameters": [
          {
            "name": "ticker",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string"
            },
            "example": "NVDA"
          },
          {
            "name": "market_factor_etf",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "default": "SPY"
            },
            "description": "Market factor ETF (passed through to the decomposition service)."
          },
          {
            "name": "years",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 15,
              "default": 1
            },
            "description": "Calendar years of daily history to return (bounds the Zarr/DB slice; default 1)."
          }
        ],
        "responses": {
          "200": {
            "description": "Columnar L3 HR/ER time series (V3).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/L3DecompositionResponse"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid Bearer token.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "402": {
            "description": "Insufficient balance.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/InsufficientBalance"
                }
              }
            }
          },
          "404": {
            "description": "Ticker not found.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded.",
            "headers": {
              "Retry-After": {
                "schema": {
                  "type": "integer"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/RateLimitExceeded"
                }
              }
            }
          }
        }
      }
    },
    "/lstar": {
      "get": {
        "summary": "Recommended hedge level (L*) per date with dispatched HRs",
        "description": "Per-(ticker, date) recommended hedge level — the simplest level whose marginal explained-return clears the threshold:\n\n  • if `L3_subsector_ER ≥ θ` → `L3` (3-ETF hedge: market + sector + subsector)\n  • elif `L2_sector_ER ≥ θ` → `L2` (2-ETF hedge: market + sector)\n  • else                    → `L1` (1-ETF hedge: market only)\n\nDefault `θ = 0.01` (1%). The chat-facing default is server-authoritative; SDK callers can override via `?threshold=`. Response carries the chosen level's market / sector / subsector hedge ratios (nulls below the chosen level), the chosen level's total ER and daily residual return (`residual_return`), and the raw `l2_sector_er` / `l3_subsector_er` inputs for audit. See the side study at `RM_ORG/content/Medium/series/drafts/lstar_selection/` for why 1% is the default.\n**Billing:** $0.02 + $0.01 per extra year.\n",
        "operationId": "getLstar",
        "tags": [
          "Risk Metrics"
        ],
        "x-pricing": {
          "capability_id": "lstar",
          "tier": "premium",
          "model": "per_request",
          "cost_usd": 0.02,
          "cost_per_extra_year_usd": 0.01,
          "currency": "USD",
          "billing_code": "lstar_v2"
        },
        "parameters": [
          {
            "name": "ticker",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string"
            },
            "example": "AAPL"
          },
          {
            "name": "market_factor_etf",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "default": "SPY"
            },
            "description": "Market factor ETF passed through to the underlying L1/L2/L3 fields."
          },
          {
            "name": "years",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 15,
              "default": 1
            },
            "description": "Calendar years of daily history."
          },
          {
            "name": "threshold",
            "in": "query",
            "required": false,
            "schema": {
              "type": "number",
              "minimum": 0,
              "maximum": 0.5,
              "default": 0.01
            },
            "description": "Marginal-ER threshold for the L*/L1/L2/L3 selection rule. Default 0.01 (1%). Chat / agentic surfaces should leave at the default; SDK callers may tune. Backtests across 275 tickers × 11 sectors found no compelling reason for per-sector thresholds.\n"
          },
          {
            "name": "axis",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "industry"
              ],
              "default": "industry"
            },
            "description": "Cascade axis. `industry` (default): sector/subsector marginal ERs and ETF hedge ratios. DEPRECATED value `style` was removed in v4 and now returns 400 — style is a diagnostic block served by `POST /v4/decompose`; the industry cascade is the only hedge axis.\n"
          }
        ],
        "responses": {
          "200": {
            "description": "Per-date recommended level + dispatched hedge ratios.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/LstarResponse"
                }
              }
            }
          },
          "400": {
            "description": "Invalid request.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid Bearer token.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "402": {
            "description": "Insufficient balance.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/InsufficientBalance"
                }
              }
            }
          },
          "404": {
            "description": "Ticker not found.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded.",
            "headers": {
              "Retry-After": {
                "schema": {
                  "type": "integer"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/RateLimitExceeded"
                }
              }
            }
          }
        }
      }
    },
    "/residual-signal/{ticker}": {
      "get": {
        "summary": "Residual mean-reversion factor — snapshot + history for one ticker",
        "description": "Phase D residual mean-reversion factor. `residual_z_5d` is the trailing 5-day cumulative L3 orthogonal residual return, z-scored by the stock's own 60-day residual volatility (negative = oversold, positive = overbought).\n\nA combo-input factor building block for multi-signal alpha stacks — NOT a standalone strategy. Gross Sharpe ~0.79 (decile long-short, 5-day horizon), ~1.28 within the high-subsector-ER quintile; net of market-impact costs, standalone capacity caps near ~$1M book. Every response carries `capacity_note` with this disclosure.\n\n**Billing:** $0.02 per request.\n",
        "operationId": "getResidualSignal",
        "tags": [
          "Risk Metrics"
        ],
        "x-pricing": {
          "capability_id": "residual-signal",
          "tier": "premium",
          "model": "per_request",
          "cost_usd": 0.02,
          "currency": "USD",
          "billing_code": "residual_signal_v2"
        },
        "parameters": [
          {
            "name": "ticker",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "example": "AAPL"
          },
          {
            "name": "days",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 730,
              "default": 90
            },
            "description": "Calendar-day lookback for the returned history window."
          }
        ],
        "responses": {
          "200": {
            "description": "Latest signal reading + history window.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "ticker": {
                      "type": "string"
                    },
                    "as_of_date": {
                      "type": "string",
                      "format": "date"
                    },
                    "signal": {
                      "$ref": "#/components/schemas/ResidualSignalReading"
                    },
                    "history": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/ResidualSignalReading"
                      }
                    },
                    "capacity_note": {
                      "type": "string"
                    },
                    "methodology_link": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Invalid request.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid Bearer token.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "402": {
            "description": "Insufficient balance.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/InsufficientBalance"
                }
              }
            }
          },
          "404": {
            "description": "Ticker not in the active universe.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded.",
            "headers": {
              "Retry-After": {
                "schema": {
                  "type": "integer"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/RateLimitExceeded"
                }
              }
            }
          }
        }
      }
    },
    "/residual-signal/latest": {
      "get": {
        "summary": "Residual mean-reversion factor — active-universe cross-section",
        "description": "Full active-universe residual mean-reversion cross-section at the latest trading day, sorted by `residual_z_5d` ascending (most oversold first), paginated. Combo-input factor — see `/residual-signal/{ticker}` for the framing and capacity disclosure.\n\n**Billing:** $0.02 per request.\n",
        "operationId": "getResidualSignalLatest",
        "tags": [
          "Risk Metrics"
        ],
        "x-pricing": {
          "capability_id": "residual-signal",
          "tier": "premium",
          "model": "per_request",
          "cost_usd": 0.02,
          "currency": "USD",
          "billing_code": "residual_signal_v2"
        },
        "parameters": [
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 2000,
              "default": 500
            }
          },
          {
            "name": "offset",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 0,
              "default": 0
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Paginated universe cross-section.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "as_of_date": {
                      "type": "string",
                      "format": "date"
                    },
                    "count": {
                      "type": "integer"
                    },
                    "total": {
                      "type": "integer"
                    },
                    "rows": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/ResidualSignalRow"
                      }
                    },
                    "capacity_note": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid Bearer token.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "402": {
            "description": "Insufficient balance.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/InsufficientBalance"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded.",
            "headers": {
              "Retry-After": {
                "schema": {
                  "type": "integer"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/RateLimitExceeded"
                }
              }
            }
          }
        }
      }
    },
    "/residual-signal/decile/{n}": {
      "get": {
        "summary": "Residual mean-reversion factor — members of decile n",
        "description": "Current members of residual-signal decile `n` (1 = most oversold, 10 = most overbought), sorted by `residual_z_5d`. Combo-input factor — see `/residual-signal/{ticker}` for the framing and capacity disclosure.\n\n**Billing:** $0.02 per request.\n",
        "operationId": "getResidualSignalDecile",
        "tags": [
          "Risk Metrics"
        ],
        "x-pricing": {
          "capability_id": "residual-signal",
          "tier": "premium",
          "model": "per_request",
          "cost_usd": 0.02,
          "currency": "USD",
          "billing_code": "residual_signal_v2"
        },
        "parameters": [
          {
            "name": "n",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 10
            },
            "example": 1
          }
        ],
        "responses": {
          "200": {
            "description": "Decile members.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "as_of_date": {
                      "type": "string",
                      "format": "date"
                    },
                    "decile": {
                      "type": "integer"
                    },
                    "count": {
                      "type": "integer"
                    },
                    "rows": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/ResidualSignalRow"
                      }
                    },
                    "capacity_note": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Invalid decile (must be 1-10).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid Bearer token.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "402": {
            "description": "Insufficient balance.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/InsufficientBalance"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded.",
            "headers": {
              "Retry-After": {
                "schema": {
                  "type": "integer"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/RateLimitExceeded"
                }
              }
            }
          }
        }
      }
    },
    "/signals/residual-reversion/basket": {
      "post": {
        "summary": "Residual mean-reversion factor — user-defined basket aggregate",
        "description": "Aggregate the Phase D L3 residual mean-reversion signal across a user-supplied basket of up to **500 tickers**. Returns the weighted aggregate of `residual_z_5d`, `signal_strength`, `industry_percentile`, `residual_autocorr_5d`, and `l3_subsector_er`, plus decile and signal-quality-quintile histograms and per-member rows. Equal-weight default; supply `weights[]` aligned 1:1 with `tickers` for custom weighting. Optional `signal_quality_min_quintile` (1–5) gates members below that quintile out of the aggregate while keeping them visible in the `members[]` rows.\n\n**Trust contract:** tickers not present in `ds_erm3_residual_signal` at the latest teo are silently dropped — the upstream mask is the source of truth for \"good\" rows. The response's `coverage` block surfaces what was requested, what landed in the basket, and which tickers were missing.\n\nCompanion to:\n- `GET /residual-signal/{ticker}` — per-name snapshot + history.\n- `GET /residual-signal/latest` — full universe cross-section.\n- `GET /residual-signal/decile/{n}` — one decile bucket.\n\nCombo-input factor — see the per-ticker route for the framing and capacity disclosure that's repeated on every response.\n\n**Billing:** $0.02 per request.\n",
        "operationId": "postResidualSignalBasket",
        "tags": [
          "Risk Metrics"
        ],
        "x-pricing": {
          "capability_id": "residual-signal-basket",
          "tier": "premium",
          "model": "per_request",
          "cost_usd": 0.02,
          "currency": "USD",
          "billing_code": "residual_signal_basket_v2"
        },
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "tickers"
                ],
                "properties": {
                  "tickers": {
                    "type": "array",
                    "minItems": 1,
                    "maxItems": 500,
                    "items": {
                      "type": "string"
                    },
                    "description": "Symbols to aggregate (1–500). Aliases (e.g. GOOGL→GOOG) resolved."
                  },
                  "weights": {
                    "type": "array",
                    "items": {
                      "type": "number",
                      "minimum": 0
                    },
                    "description": "Optional non-negative weights aligned 1:1 with `tickers`. Equal-weight when omitted. Sum need not be 1 — the aggregator normalizes.\n"
                  },
                  "signal_quality_min_quintile": {
                    "type": "integer",
                    "minimum": 1,
                    "maximum": 5,
                    "description": "Optional gate on `signal_quality_quintile`. Phase B: gross Sharpe rises from ~0.79 (universe) to ~1.28 at quintile 5. Members below this still appear in `members[]` with `passed_quality_gate=false` but do NOT contribute to the aggregate.\n"
                  }
                }
              },
              "examples": {
                "equalWeight": {
                  "summary": "Equal-weight Mag 7 basket",
                  "value": {
                    "tickers": [
                      "AAPL",
                      "MSFT",
                      "NVDA",
                      "META",
                      "GOOG",
                      "AMZN",
                      "TSLA"
                    ]
                  }
                },
                "qualityGated": {
                  "summary": "Top-quality-quintile only",
                  "value": {
                    "tickers": [
                      "AAPL",
                      "MSFT",
                      "NVDA",
                      "META",
                      "GOOG",
                      "AMZN",
                      "TSLA"
                    ],
                    "signal_quality_min_quintile": 5
                  }
                },
                "customWeights": {
                  "summary": "Custom weights (cap-style)",
                  "value": {
                    "tickers": [
                      "AAPL",
                      "MSFT",
                      "NVDA"
                    ],
                    "weights": [
                      3.1,
                      2.9,
                      2.5
                    ]
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Basket aggregate, distributions, and per-member rows.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "as_of_date": {
                      "type": "string",
                      "format": "date"
                    },
                    "aggregate": {
                      "type": "object",
                      "properties": {
                        "residual_z_5d": {
                          "type": "number",
                          "nullable": true
                        },
                        "signal_strength": {
                          "type": "number",
                          "nullable": true
                        },
                        "industry_percentile": {
                          "type": "number",
                          "nullable": true
                        },
                        "residual_autocorr_5d": {
                          "type": "number",
                          "nullable": true
                        },
                        "l3_subsector_er": {
                          "type": "number",
                          "nullable": true
                        },
                        "decile_distribution": {
                          "type": "object",
                          "additionalProperties": {
                            "type": "integer"
                          },
                          "description": "Count of members per decile bucket (keys \"1\"–\"10\" and \"null\"). Includes every in-zarr member regardless of the quality gate.\n"
                        },
                        "quality_quintile_distribution": {
                          "type": "object",
                          "additionalProperties": {
                            "type": "integer"
                          },
                          "description": "Count of members per signal_quality_quintile bucket (keys \"1\"–\"5\" and \"null\").\n"
                        }
                      }
                    },
                    "coverage": {
                      "type": "object",
                      "properties": {
                        "requested": {
                          "type": "integer"
                        },
                        "in_zarr": {
                          "type": "integer",
                          "description": "Members present in ds_erm3_residual_signal at this teo."
                        },
                        "contributed": {
                          "type": "integer",
                          "description": "In-zarr members that also passed the optional quality gate."
                        },
                        "weight_covered": {
                          "type": "number",
                          "description": "Sum of input weights for contributing members."
                        },
                        "missing_tickers": {
                          "type": "array",
                          "items": {
                            "type": "string"
                          },
                          "description": "Tickers asked for but absent from the zarr."
                        }
                      }
                    },
                    "members": {
                      "type": "array",
                      "items": {
                        "allOf": [
                          {
                            "$ref": "#/components/schemas/ResidualSignalRow"
                          },
                          {
                            "type": "object",
                            "properties": {
                              "weight": {
                                "type": "number"
                              },
                              "in_zarr": {
                                "type": "boolean"
                              },
                              "passed_quality_gate": {
                                "type": "boolean"
                              }
                            }
                          }
                        ]
                      }
                    },
                    "capacity_note": {
                      "type": "string"
                    },
                    "methodology_link": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Invalid request body (missing tickers, weights length mismatch, etc).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid Bearer token.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "402": {
            "description": "Insufficient balance.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/InsufficientBalance"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded.",
            "headers": {
              "Retry-After": {
                "schema": {
                  "type": "integer"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/RateLimitExceeded"
                }
              }
            }
          }
        }
      }
    },
    "/universe/{name}/members": {
      "get": {
        "summary": "Active members of a named universe at a teo",
        "description": "Active membership of a named universe (e.g. `uni_mc_3000`) at one trading day (latest by default). **Active = monthly universe_mask AND daily validity gate** — matches the same dual-gate the ERM3 pipeline applies to produce its output zarrs. Symbols failing either gate are NOT in the response.\n\nThe universe mask is stamped at month-end and applied to every teo in the following month; validity is per-day. The response includes a `mask_as_of` month-end stamp so callers can disambiguate \"membership changed because new month\" from \"membership changed because daily validity failed\" — useful when tracking turnover over time.\n\nFoundational endpoint for any cross-sectional workflow that needs to align on the canonical universe without the Python SDK.\n\n**Billing:** $0.02 per request.\n",
        "operationId": "getUniverseMembers",
        "tags": [
          "Risk Metrics"
        ],
        "x-pricing": {
          "capability_id": "universe-members",
          "tier": "baseline",
          "model": "per_request",
          "cost_usd": 0.02,
          "currency": "USD",
          "billing_code": "universe_members_v2"
        },
        "parameters": [
          {
            "name": "name",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "enum": [
                "uni_mc_50",
                "uni_mc_500",
                "uni_mc_1000",
                "uni_mc_3000",
                "uni_dv_50",
                "uni_dv_500",
                "uni_dv_1000",
                "uni_dv_3000"
              ]
            },
            "description": "Universe label from the KNOWN_UNIVERSES registry.",
            "example": "uni_mc_3000"
          },
          {
            "name": "teo",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "format": "date"
            },
            "description": "Observation date YYYY-MM-DD (default latest teo)."
          }
        ],
        "responses": {
          "200": {
            "description": "Active members + counts breakdown + mask_as_of.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "universe": {
                      "type": "string"
                    },
                    "teo": {
                      "type": "string",
                      "format": "date"
                    },
                    "mask_as_of": {
                      "type": "string",
                      "format": "date",
                      "description": "Month-end stamp the monthly universe mask was applied. Use to disambiguate mask-vs-validity-driven membership changes between two teos.\n"
                    },
                    "members": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "symbol": {
                            "type": "string"
                          },
                          "ticker": {
                            "type": "string"
                          }
                        }
                      }
                    },
                    "counts": {
                      "type": "object",
                      "properties": {
                        "active": {
                          "type": "integer",
                          "description": "Universe mask AND validity at this teo (== members.length)."
                        },
                        "in_universe_mask": {
                          "type": "integer",
                          "description": "Members of the monthly universe mask BEFORE applying daily validity."
                        },
                        "inactive_from_validity": {
                          "type": "integer",
                          "description": "Symbols that pass the universe gate but fail the daily validity gate."
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Unknown universe label (not in KNOWN_UNIVERSES) or invalid teo format.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid Bearer token.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "402": {
            "description": "Insufficient balance.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/InsufficientBalance"
                }
              }
            }
          },
          "404": {
            "description": "Universe membership unavailable for the requested label / teo.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded.",
            "headers": {
              "Retry-After": {
                "schema": {
                  "type": "integer"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/RateLimitExceeded"
                }
              }
            }
          }
        }
      }
    },
    "/etf/factor-returns": {
      "get": {
        "summary": "ETF factor returns snapshot (public scope)",
        "description": "One-teo snapshot of close + trailing-window total returns (1d / 21d / 63d / 252d) for **SPY + the 11 GICS sector SPDR ETFs** (XLE / XLB / XLI / XLY / XLP / XLV / XLF / XLK / XLC / XLU / XLRE).\n\nThe classification scope is intentionally narrow — GICS sector membership is industry-public knowledge so exposing it carries no leakage on top of what any prospectus already discloses. The fuller BWMACRO factor roster (subsectors, style, macro, broad-market) is NOT exposed through this endpoint by design. Tickers requested outside the public scope return **400**.\n\nPairs well with `/industry-panel` to read the day's market and sector index moves alongside aggregate stock-level industry βs.\n\n**Billing:** $0.02 per request.\n",
        "operationId": "getEtfFactorReturns",
        "tags": [
          "Risk Metrics"
        ],
        "x-pricing": {
          "capability_id": "etf-factor-returns",
          "tier": "baseline",
          "model": "per_request",
          "cost_usd": 0.02,
          "currency": "USD",
          "billing_code": "etf_factor_returns_v2"
        },
        "parameters": [
          {
            "name": "sleeve",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "market",
                "sector",
                "all"
              ],
              "default": "all"
            },
            "description": "Filter to market (SPY only), sector (11 GICS sectors), or all (default)."
          },
          {
            "name": "tickers",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            },
            "description": "Comma-separated subset of in-scope tickers (intersected with sleeve filter). Tickers outside SPY + 11 GICS sectors return 400.\n",
            "example": "SPY,XLK,XLF"
          },
          {
            "name": "teo",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "format": "date"
            },
            "description": "Observation date YYYY-MM-DD (default latest teo in ds_etf)."
          }
        ],
        "responses": {
          "200": {
            "description": "Snapshot with one row per in-scope ETF.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "teo": {
                      "type": "string",
                      "format": "date"
                    },
                    "filter": {
                      "type": "object",
                      "properties": {
                        "sleeve": {
                          "type": "string",
                          "enum": [
                            "market",
                            "sector",
                            "all"
                          ]
                        },
                        "tickers": {
                          "type": "array",
                          "nullable": true,
                          "items": {
                            "type": "string"
                          }
                        }
                      }
                    },
                    "windows": {
                      "type": "array",
                      "items": {
                        "type": "string"
                      },
                      "description": "Window labels covered by `returns` (1d, 21d, 63d, 252d)."
                    },
                    "rows": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "ticker": {
                            "type": "string"
                          },
                          "sleeve": {
                            "type": "string",
                            "enum": [
                              "market",
                              "sector"
                            ]
                          },
                          "name": {
                            "type": "string"
                          },
                          "close": {
                            "type": "number",
                            "nullable": true
                          },
                          "returns": {
                            "type": "object",
                            "properties": {
                              "1d": {
                                "type": "number",
                                "nullable": true
                              },
                              "21d": {
                                "type": "number",
                                "nullable": true
                              },
                              "63d": {
                                "type": "number",
                                "nullable": true
                              },
                              "252d": {
                                "type": "number",
                                "nullable": true
                              }
                            }
                          }
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Unknown ticker (outside public scope) or invalid teo format.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid Bearer token.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "402": {
            "description": "Insufficient balance.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/InsufficientBalance"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded.",
            "headers": {
              "Retry-After": {
                "schema": {
                  "type": "integer"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/RateLimitExceeded"
                }
              }
            }
          },
          "503": {
            "description": "Upstream ETF zarr unavailable.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/correlation": {
      "post": {
        "summary": "Correlation vs macro factor returns",
        "description": "Computes Pearson or Spearman correlation between a stock return series and daily macro factor returns stored in `macro_factors` (e.g. bitcoin, vix). Stock series may be gross returns or ERM3 residual returns (L1/L2/L3) using the replication identity with SPY and sector/subsector ETFs. Request body accepts a single ticker or an array for batch (`results` array). Cost: $0.01 per request. Factor strings are normalized to lowercase canonical keys matching `macro_factors.factor_key` (aliases such as btc → bitcoin). JSON Schema (MCP / validation): request body https://riskmodels.app/schemas/factor-correlation-request-v1.json ; single-ticker 200 body https://riskmodels.app/schemas/factor-correlation-v1.json .\n",
        "operationId": "postFactorCorrelation",
        "externalDocs": {
          "description": "JSON Schema for POST /correlation request body (MCP validate before call).",
          "url": "https://riskmodels.app/schemas/factor-correlation-request-v1.json"
        },
        "tags": [
          "Risk Metrics"
        ],
        "x-pricing": {
          "capability_id": "factor-correlation",
          "tier": "baseline",
          "model": "per_request",
          "cost_usd": 0.01,
          "currency": "USD",
          "billing_code": "factor_correlation_v2"
        },
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/FactorCorrelationRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Correlations per macro factor (null when insufficient overlap or missing data).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/FactorCorrelationResponse"
                }
              }
            }
          },
          "400": {
            "description": "Invalid body.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid Bearer token.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "402": {
            "description": "Insufficient balance.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/InsufficientBalance"
                }
              }
            }
          },
          "404": {
            "description": "Ticker not found or insufficient history.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/RateLimitExceeded"
                }
              }
            }
          }
        }
      }
    },
    "/metrics/{ticker}/correlation": {
      "get": {
        "summary": "Correlation vs macro factors (query convenience)",
        "description": "Same computation as POST /correlation. Pass macro keys as a comma-separated `factors` query param (alias: `factor` is accepted as a synonym).\n",
        "operationId": "getFactorCorrelationByTicker",
        "externalDocs": {
          "description": "JSON Schema for the single-ticker success body (correlations, overlap_days, warnings, optional _metadata / _agent).",
          "url": "https://riskmodels.app/schemas/factor-correlation-v1.json"
        },
        "tags": [
          "Risk Metrics"
        ],
        "x-pricing": {
          "capability_id": "factor-correlation",
          "tier": "baseline",
          "model": "per_request",
          "cost_usd": 0.01,
          "currency": "USD",
          "billing_code": "factor_correlation_v2"
        },
        "parameters": [
          {
            "name": "ticker",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "example": "NVDA"
          },
          {
            "name": "factors",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            },
            "description": "Comma-separated factor keys (bitcoin,gold,...); default all six. Same as query param `factor`."
          },
          {
            "name": "return_type",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "gross",
                "l1",
                "l2",
                "l3_residual"
              ],
              "default": "l3_residual"
            }
          },
          {
            "name": "window_days",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 20,
              "maximum": 2000,
              "default": 252
            }
          },
          {
            "name": "method",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "pearson",
                "spearman"
              ],
              "default": "pearson"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Correlations and lineage metadata.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/FactorCorrelationSingleResponse"
                }
              }
            }
          },
          "400": {
            "description": "Invalid ticker or query.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid Bearer token.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "402": {
            "description": "Insufficient balance.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/InsufficientBalance"
                }
              }
            }
          },
          "404": {
            "description": "Ticker not found or insufficient history.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/RateLimitExceeded"
                }
              }
            }
          }
        }
      }
    },
    "/macro-factors": {
      "get": {
        "summary": "Macro factor daily return series",
        "description": "Returns long-format rows from Supabase `macro_factors` (`factor_key`, `teo`, `return_gross`) for the requested date range. No stock ticker is required. Omit `factors` to use all ten canonical keys (`inflation`, `term_spread`, `short_rates`, `credit`, `oil`, `gold`, `usd`, `volatility`, `bitcoin`, `vix_spot`); aliases such as `btc` → `bitcoin`, `xau`/`gld` → `gold`, and legacy v1 names (`dxy` → `usd`, `vix` → `vix_spot`, `ust10y2y` → `term_spread`) normalize automatically. Default date range: from five calendar years before `end` through `end` (UTC date); default `end` is today. Maximum span: 20 years. Cost: $0.005 per request. JSON Schema (MCP): https://riskmodels.app/schemas/macro-factors-series-v1.json\n",
        "operationId": "getMacroFactorSeries",
        "externalDocs": {
          "description": "JSON Schema for GET /macro-factors 200 response body.",
          "url": "https://riskmodels.app/schemas/macro-factors-series-v1.json"
        },
        "tags": [
          "Risk Metrics"
        ],
        "x-pricing": {
          "capability_id": "macro-factor-series",
          "tier": "baseline",
          "model": "per_request",
          "cost_usd": 0.005,
          "currency": "USD",
          "billing_code": "macro_factor_series_v2"
        },
        "security": [
          {
            "BearerAuth": []
          },
          {
            "OAuth2AuthorizationCode": [
              "mcp:read"
            ]
          }
        ],
        "parameters": [
          {
            "name": "factors",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            },
            "description": "Comma-separated factor keys; synonym query param `factor` is also accepted by the server."
          },
          {
            "name": "start",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "format": "date"
            },
            "description": "Inclusive start (YYYY-MM-DD)."
          },
          {
            "name": "end",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "format": "date"
            },
            "description": "Inclusive end (YYYY-MM-DD). Defaults to today (UTC)."
          }
        ],
        "responses": {
          "200": {
            "description": "Macro factor rows and lineage metadata.",
            "headers": {
              "X-API-Cost-USD": {
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/MacroFactorsSeriesResponse"
                }
              }
            }
          },
          "400": {
            "description": "Invalid query (date range, factors, or span).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid Bearer token.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "402": {
            "description": "Insufficient balance.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/InsufficientBalance"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/RateLimitExceeded"
                }
              }
            }
          }
        }
      }
    },
    "/rankings/{ticker}": {
      "get": {
        "summary": "Cross-sectional rankings snapshot",
        "description": "Latest V3 cross-sectional ranks from `security_history` for the ticker’s symbol. Optional query filters narrow to one metric, cohort, and/or window; omit them for the full grid. See `#/components/schemas/RankingMetricKeys` for key patterns. `rank_percentile` is 100 = best.\n",
        "operationId": "getRankingsByTicker",
        "tags": [
          "Risk Metrics"
        ],
        "x-pricing": {
          "capability_id": "rankings",
          "tier": "baseline",
          "model": "per_request",
          "cost_usd": 0.005,
          "currency": "USD",
          "billing_code": "rankings_v4"
        },
        "parameters": [
          {
            "name": "ticker",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "example": "AAPL"
          },
          {
            "name": "metric",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "mkt_cap",
                "gross_return",
                "sector_residual",
                "subsector_residual",
                "er_l1",
                "er_l2",
                "er_l3"
              ]
            }
          },
          {
            "name": "cohort",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "universe",
                "sector",
                "subsector"
              ]
            }
          },
          {
            "name": "window",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "1d",
                "21d",
                "63d",
                "252d"
              ]
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Rankings and lineage metadata.",
            "headers": {
              "X-API-Cost-USD": {
                "schema": {
                  "type": "string"
                }
              },
              "X-Data-Fetch-Latency-Ms": {
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "ticker": {
                      "type": "string"
                    },
                    "symbol": {
                      "type": "string"
                    },
                    "teo": {
                      "type": "string",
                      "format": "date",
                      "nullable": true
                    },
                    "rankings": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "metric": {
                            "type": "string"
                          },
                          "cohort": {
                            "type": "string"
                          },
                          "window": {
                            "type": "string"
                          },
                          "rank_ordinal": {
                            "type": "integer",
                            "nullable": true
                          },
                          "cohort_size": {
                            "type": "integer",
                            "nullable": true
                          },
                          "rank_percentile": {
                            "type": "number",
                            "nullable": true
                          }
                        }
                      }
                    },
                    "filters": {
                      "type": "object",
                      "properties": {
                        "metric": {
                          "type": "string",
                          "nullable": true
                        },
                        "cohort": {
                          "type": "string",
                          "nullable": true
                        },
                        "window": {
                          "type": "string",
                          "nullable": true
                        }
                      }
                    },
                    "_metadata": {
                      "type": "object",
                      "additionalProperties": true
                    },
                    "_agent": {
                      "$ref": "#/components/schemas/AgentMeta"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Invalid query parameter.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid Bearer token.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "402": {
            "description": "Insufficient balance.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/InsufficientBalance"
                }
              }
            }
          },
          "404": {
            "description": "Ticker not found.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/RateLimitExceeded"
                }
              }
            }
          }
        }
      }
    },
    "/rankings/{ticker}/badge": {
      "get": {
        "summary": "Shields.io-style rank badge (JSON)",
        "description": "Public JSON for [Shields Endpoint badges](https://shields.io/badges/endpoint-badge). No API key by default; optional shared secret via env `RANKINGS_BADGE_TOKEN` and query `token=`. Defaults: metric=subsector_residual, cohort=subsector, window=252d. `rank_percentile` 100 = best. Cached at the edge; per-IP rate limit when Upstash Redis is configured (`RANKINGS_BADGE_IP_RPM`, default 120).\n",
        "operationId": "getRankingsBadge",
        "tags": [
          "Risk Metrics"
        ],
        "x-pricing": {
          "capability_id": "rankings",
          "tier": "baseline",
          "model": "per_request",
          "cost_usd": 0.005,
          "currency": "USD",
          "billing_code": "rankings_v4"
        },
        "parameters": [
          {
            "name": "ticker",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "example": "AAPL"
          },
          {
            "name": "token",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            },
            "description": "Required when server sets RANKINGS_BADGE_TOKEN."
          },
          {
            "name": "metric",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "mkt_cap",
                "gross_return",
                "sector_residual",
                "subsector_residual",
                "er_l1",
                "er_l2",
                "er_l3"
              ],
              "default": "subsector_residual"
            }
          },
          {
            "name": "cohort",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "universe",
                "sector",
                "subsector"
              ],
              "default": "subsector"
            }
          },
          {
            "name": "window",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "1d",
                "21d",
                "63d",
                "252d"
              ],
              "default": "252d"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Shields endpoint JSON (schemaVersion 1).",
            "headers": {
              "Cache-Control": {
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "schemaVersion"
                  ],
                  "properties": {
                    "schemaVersion": {
                      "type": "integer",
                      "enum": [
                        1
                      ]
                    },
                    "label": {
                      "type": "string"
                    },
                    "message": {
                      "type": "string"
                    },
                    "color": {
                      "type": "string"
                    },
                    "isError": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Invalid ticker or query parameter."
          },
          "401": {
            "description": "Missing or invalid token when RANKINGS_BADGE_TOKEN is set."
          },
          "404": {
            "description": "Ticker or ranking row not found."
          }
        }
      }
    },
    "/rankings/top": {
      "get": {
        "summary": "Cross-sectional rankings leaderboard",
        "description": "Best names (lowest rank_ordinal) at the latest `teo` for one metric × cohort × window. Same `rank_percentile` rule as GET /rankings/{ticker} (100 = best). Requires all three filters.\n",
        "operationId": "getRankingsTop",
        "tags": [
          "Risk Metrics"
        ],
        "x-pricing": {
          "capability_id": "rankings",
          "tier": "baseline",
          "model": "per_request",
          "cost_usd": 0.005,
          "currency": "USD",
          "billing_code": "rankings_v4"
        },
        "parameters": [
          {
            "name": "metric",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string",
              "enum": [
                "mkt_cap",
                "gross_return",
                "sector_residual",
                "subsector_residual",
                "er_l1",
                "er_l2",
                "er_l3"
              ]
            }
          },
          {
            "name": "cohort",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string",
              "enum": [
                "universe",
                "sector",
                "subsector"
              ]
            }
          },
          {
            "name": "window",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string",
              "enum": [
                "1d",
                "21d",
                "63d",
                "252d"
              ]
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 100,
              "default": 10
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Leaderboard rows and lineage metadata.",
            "headers": {
              "X-API-Cost-USD": {
                "schema": {
                  "type": "string"
                }
              },
              "X-Data-Fetch-Latency-Ms": {
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "teo": {
                      "type": "string",
                      "format": "date",
                      "nullable": true
                    },
                    "metric": {
                      "type": "string"
                    },
                    "cohort": {
                      "type": "string"
                    },
                    "window": {
                      "type": "string"
                    },
                    "limit": {
                      "type": "integer"
                    },
                    "rankings": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "symbol": {
                            "type": "string"
                          },
                          "ticker": {
                            "type": "string"
                          },
                          "rank_ordinal": {
                            "type": "integer"
                          },
                          "cohort_size": {
                            "type": "integer",
                            "nullable": true
                          },
                          "rank_percentile": {
                            "type": "number",
                            "nullable": true
                          }
                        }
                      }
                    },
                    "_metadata": {
                      "type": "object",
                      "additionalProperties": true
                    },
                    "_agent": {
                      "$ref": "#/components/schemas/AgentMeta"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Missing or invalid query parameter.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid Bearer token.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "402": {
            "description": "Insufficient balance.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/InsufficientBalance"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/RateLimitExceeded"
                }
              }
            }
          }
        }
      }
    },
    "/batch/analyze": {
      "post": {
        "summary": "(internal/legacy) Multi-ticker batch analysis",
        "description": "**Per Snapshot Architecture v3, prefer `POST /snapshot` for new clients.** This endpoint remains callable as an internal building block (cohort scans, parity checks, zarr alignment).\nFetch data for up to 100 tickers in one JSON response (`results` map keyed by ticker). Supported `metrics` flags: `returns` (daily arrays with `l1`/`l2`/`l3` columns — see operation schema for semantics), `hedge_ratios` (latest six hedge ratios with **short** keys `l1_market`, …), `full_metrics` (flat L1/L2/L3 ER/HR snapshot with **long** keys `l1_market_hr`, `l1_market_er`, …). Request every block you need: `metrics` is a whitelist (no silent backfill). For ERM3 zarr parity (`L*_ER` / `L*_HR`), request `[\"full_metrics\",\"hedge_ratios\"]` and see docs/ERM3_ZARR_API_PARITY.md. The `l3_decomposition` flag is accepted but may be unpopulated depending on deployment. Cost: $0.005/position, minimum $0.01/call.\n## Status code semantics (clients must read `summary` to detect partial / total failure)\nThis endpoint **always returns HTTP 200** as long as the request is well-formed and authentication passes. Per-ticker failures are reported in `results[ticker].status = \"error\"` with a human-readable `error` string, and counted in `summary.errors`. **Treat this as a 207 Multi-Status conceptually**: you cannot infer success from the HTTP code alone.\nRecommended client logic: - `summary.errors == 0` → all ok - `0 < summary.errors < summary.total` → partial; retry only the failed tickers (see `results[ticker].error`) - `summary.errors == summary.total` → total failure; surface or escalate (do **not** loop-retry the whole batch — the failure is likely structural, e.g. all tickers unresolvable or upstream zarr gap)\n4xx is reserved for malformed requests (`tickers` empty, > 100, unknown `metrics` flags, bad auth).\n**Parquet/CSV:** When `format` is `parquet` or `csv`, the response is a single long-format table of daily rows (`ticker`, `date`, `gross_return`, `l1`, `l2`, `l3`) per `#/components/schemas/BatchAnalyzeExportRow`. Only tickers with successful `returns` payloads are included; `full_metrics`, `hedge_ratios`, and `l3_decomposition` are not exported in tabular form. Ensure `metrics` includes `returns` or the table may be empty.\n",
        "operationId": "batchAnalyze",
        "tags": [
          "Risk Metrics"
        ],
        "x-pricing": {
          "capability_id": "batch-analysis",
          "tier": "premium",
          "model": "per_position",
          "cost_usd": 0.015,
          "min_charge": 0.03,
          "currency": "USD",
          "billing_code": "batch_analysis_v4"
        },
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "tickers",
                  "metrics"
                ],
                "properties": {
                  "tickers": {
                    "type": "array",
                    "items": {
                      "type": "string"
                    },
                    "maxItems": 100,
                    "description": "List of ticker symbols. Maximum 100.",
                    "example": [
                      "AAPL",
                      "MSFT",
                      "NVDA",
                      "GOOGL"
                    ]
                  },
                  "metrics": {
                    "type": "array",
                    "items": {
                      "type": "string",
                      "enum": [
                        "returns",
                        "l3_decomposition",
                        "hedge_ratios",
                        "full_metrics"
                      ]
                    },
                    "description": "Which payloads to include per ticker (`full_metrics` recommended for portfolio screens).",
                    "example": [
                      "full_metrics"
                    ]
                  },
                  "years": {
                    "type": "integer",
                    "minimum": 1,
                    "maximum": 15,
                    "default": 1,
                    "description": "Number of years of history (applies to `returns` and `l3_decomposition`)."
                  },
                  "format": {
                    "type": "string",
                    "enum": [
                      "json",
                      "parquet",
                      "csv"
                    ],
                    "default": "json",
                    "description": "`json` (default): `BatchAnalyzeResponse` with `results`, `summary`, `_agent`, `_metadata`. `parquet` / `csv`: long-format daily table only — see operation description and `#/components/schemas/BatchAnalyzeExportRow`. Requires `returns` in `metrics` for non-empty data.\n"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Batch results as JSON (`results` map) or as a single Parquet/CSV table of merged daily `returns` rows across tickers.\n",
            "headers": {
              "X-Risk-Model-Version": {
                "schema": {
                  "type": "string"
                },
                "description": "ERM3 model version (align with zarr slice / `_metadata.model_version`)."
              },
              "X-Data-As-Of": {
                "schema": {
                  "type": "string",
                  "format": "date"
                },
                "description": "Latest trading date for the batch snapshot."
              },
              "X-Factor-Set-Id": {
                "schema": {
                  "type": "string"
                },
                "description": "Factor set identifier (e.g. SPY_uni_mc_3000)."
              },
              "X-Universe-Size": {
                "schema": {
                  "type": "integer"
                },
                "description": "Universe count for this model build."
              },
              "X-API-Cost-USD": {
                "schema": {
                  "type": "string"
                }
              },
              "X-Request-ID": {
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/vnd.apache.parquet": {
                "schema": {
                  "type": "string",
                  "format": "binary"
                },
                "description": "Apache Parquet long table; columns per `#/components/schemas/BatchAnalyzeExportRow`. Tickers without `returns` data are omitted; failed tickers are omitted.\n"
              },
              "text/csv": {
                "schema": {
                  "type": "string"
                },
                "description": "UTF-8 CSV with header `ticker,date,gross_return,l1,l2,l3`; semantics match Parquet.\n",
                "example": "ticker,date,gross_return,l1,l2,l3\nAAPL,2025-03-20,0.005,0.92,0.15,0.04\nMSFT,2025-03-20,-0.002,0.88,0.11,0.03\n"
              },
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/BatchAnalyzeResponse"
                }
              }
            }
          },
          "400": {
            "description": "Invalid request body or too many tickers (> 100).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid Bearer token.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "402": {
            "description": "Insufficient balance.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/InsufficientBalance"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded.",
            "headers": {
              "Retry-After": {
                "schema": {
                  "type": "integer"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/RateLimitExceeded"
                }
              }
            }
          }
        }
      }
    },
    "/batch/lstar": {
      "post": {
        "summary": "Batch Lstar residual returns (up to 100 tickers)",
        "description": "Fetch Lstar-dispatched daily residual return and recommended hedge level (L1/L2/L3) for up to 100 tickers in one call. Uses the same marginal-ER selection rule as `GET /lstar` (default θ = 1%). Response is a `results` map keyed by upper-case ticker; per-ticker failures use `status: error` or `not_found` without failing the whole batch.\n**Billing:** $0.015/ticker + $0.0075/extra year, minimum $0.03/call.\n**Parquet/CSV:** Long-format table with columns `ticker`, `date`, `lstar`, `residual_return`, hedge ratios, and raw ER inputs. Only successful tickers with non-empty histories are included.\n",
        "operationId": "batchLstar",
        "tags": [
          "Risk Metrics"
        ],
        "x-pricing": {
          "capability_id": "batch-lstar",
          "tier": "premium",
          "model": "per_position",
          "cost_usd": 0.015,
          "cost_per_extra_year_usd": 0.0075,
          "min_charge": 0.03,
          "currency": "USD",
          "billing_code": "batch_lstar_v2"
        },
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "tickers"
                ],
                "properties": {
                  "tickers": {
                    "type": "array",
                    "items": {
                      "type": "string"
                    },
                    "minItems": 1,
                    "maxItems": 100,
                    "example": [
                      "NVDA",
                      "AAPL",
                      "MSFT"
                    ]
                  },
                  "market_factor_etf": {
                    "type": "string",
                    "default": "SPY"
                  },
                  "years": {
                    "type": "integer",
                    "minimum": 1,
                    "maximum": 15,
                    "default": 1
                  },
                  "threshold": {
                    "type": "number",
                    "minimum": 0,
                    "maximum": 0.5,
                    "default": 0.01
                  },
                  "axis": {
                    "type": "string",
                    "enum": [
                      "industry"
                    ],
                    "default": "industry",
                    "description": "Cascade axis — `industry` only. DEPRECATED value `style` was removed in v4 and now returns 400 — style is a diagnostic block served by `POST /v4/decompose`; the industry cascade is the only hedge axis.\n"
                  },
                  "format": {
                    "type": "string",
                    "enum": [
                      "json",
                      "parquet",
                      "csv"
                    ],
                    "default": "json"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Batch Lstar results as JSON or long-format Parquet/CSV.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/BatchLstarResponse"
                }
              },
              "application/vnd.apache.parquet": {
                "schema": {
                  "type": "string",
                  "format": "binary"
                }
              },
              "text/csv": {
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "400": {
            "description": "Invalid request.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "402": {
            "description": "Insufficient balance.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/InsufficientBalance"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/RateLimitExceeded"
                }
              }
            }
          }
        }
      }
    },
    "/portfolio/risk-index": {
      "post": {
        "summary": "Portfolio risk index (L3 variance decomposition)",
        "description": "Holdings-weighted L3 explained-risk decomposition (market, sector, subsector, residual), approximate portfolio volatility (weighted `vol_23d`), and optional daily time series of portfolio ER over `years`. Partial resolution: unresolved tickers appear in `errors`; computation uses resolved positions only. Metered; includes `_agent` and `_metadata`.\n**Billing:** $0.15 per request (empty `positions` / syncing placeholder responses are not charged).\n",
        "operationId": "postPortfolioRiskIndex",
        "tags": [
          "Risk Metrics"
        ],
        "x-pricing": {
          "capability_id": "portfolio-risk-index",
          "tier": "premium",
          "model": "per_request",
          "cost_usd": 0.15,
          "currency": "USD",
          "billing_code": "portfolio_risk_index_v3"
        },
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "positions"
                ],
                "properties": {
                  "positions": {
                    "type": "array",
                    "maxItems": 100,
                    "items": {
                      "type": "object",
                      "required": [
                        "ticker",
                        "weight"
                      ],
                      "properties": {
                        "ticker": {
                          "type": "string"
                        },
                        "weight": {
                          "type": "number",
                          "format": "float",
                          "exclusiveMinimum": 0,
                          "description": "Fractional weights (normalized server-side) or dollar notionals."
                        }
                      }
                    },
                    "description": "May be empty when holdings are not yet available (e.g. Plaid initial sync). In that case the API returns HTTP 200 with `status: \"syncing\"` and a message instead of portfolio aggregates.\n"
                  },
                  "timeSeries": {
                    "type": "boolean",
                    "default": false,
                    "description": "When true, include `time_series` of daily portfolio ER."
                  },
                  "years": {
                    "type": "integer",
                    "minimum": 1,
                    "maximum": 15,
                    "default": 1,
                    "description": "History span when `timeSeries` is true."
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Portfolio aggregates, per-ticker breakdown, optional time series; or status \"syncing\" when positions is empty (Plaid / holdings not ready).",
            "headers": {
              "X-API-Cost-USD": {
                "schema": {
                  "type": "string"
                }
              },
              "X-Data-Fetch-Latency-Ms": {
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "status": {
                      "type": "string",
                      "enum": [
                        "syncing"
                      ],
                      "description": "Present only when `positions` was empty (holdings not ready)."
                    },
                    "message": {
                      "type": "string",
                      "description": "Human-readable hint when status is syncing."
                    },
                    "portfolio_risk_index": {
                      "type": "object",
                      "properties": {
                        "variance_decomposition": {
                          "type": "object",
                          "properties": {
                            "market": {
                              "type": "number",
                              "nullable": true
                            },
                            "sector": {
                              "type": "number",
                              "nullable": true
                            },
                            "subsector": {
                              "type": "number",
                              "nullable": true
                            },
                            "residual": {
                              "type": "number",
                              "nullable": true
                            },
                            "systematic": {
                              "type": "number",
                              "nullable": true
                            }
                          }
                        },
                        "portfolio_volatility_23d": {
                          "type": "number",
                          "nullable": true
                        },
                        "position_count": {
                          "type": "integer"
                        }
                      }
                    },
                    "per_ticker": {
                      "type": "object",
                      "additionalProperties": true
                    },
                    "summary": {
                      "type": "object",
                      "properties": {
                        "total_positions": {
                          "type": "integer"
                        },
                        "resolved": {
                          "type": "integer"
                        },
                        "errors": {
                          "type": "integer"
                        }
                      }
                    },
                    "time_series": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "date": {
                            "type": "string",
                            "format": "date"
                          },
                          "market_er": {
                            "type": "number"
                          },
                          "sector_er": {
                            "type": "number"
                          },
                          "subsector_er": {
                            "type": "number"
                          },
                          "residual_er": {
                            "type": "number"
                          },
                          "systematic_er": {
                            "type": "number"
                          }
                        }
                      }
                    },
                    "errors": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "ticker": {
                            "type": "string"
                          },
                          "error": {
                            "type": "string"
                          }
                        }
                      }
                    },
                    "_agent": {
                      "$ref": "#/components/schemas/AgentMeta"
                    },
                    "_metadata": {
                      "type": "object",
                      "additionalProperties": true
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Invalid body or no resolvable tickers.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid Bearer token.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "402": {
            "description": "Insufficient balance.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/InsufficientBalance"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded.",
            "headers": {
              "Retry-After": {
                "schema": {
                  "type": "integer"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/RateLimitExceeded"
                }
              }
            }
          }
        }
      }
    },
    "/portfolio/risk-snapshot": {
      "post": {
        "summary": "(legacy) Portfolio risk snapshot (bundled PDF or JSON)",
        "description": "**Per Snapshot Architecture v3, prefer `POST /snapshot` (JSON) for new clients.** This endpoint remains the canonical surface for the bundled **PDF** report; for JSON, `/snapshot` returns the same shape with stronger v3 contract guarantees. One bundled report for a weighted portfolio: L3 explained-risk decomposition, per-name hedge ratios, and volatility. Uses internal data access only (no separate charges for underlying metrics calls). `format=json` returns structured data; `format=pdf` returns `application/pdf`. `format=png` is not implemented yet (501). Identical requests are cached 24 hours per user; cache hits return `X-Cache: HIT` and `X-API-Cost-USD: 0`.\n**Billing:** $1.25 per successful response (not charged on cache hit).\n",
        "operationId": "postPortfolioRiskSnapshot",
        "tags": [
          "Risk Metrics"
        ],
        "x-pricing": {
          "capability_id": "portfolio-risk-snapshot",
          "tier": "premium",
          "model": "per_request",
          "cost_usd": 1.25,
          "currency": "USD",
          "billing_code": "risk_snapshot_pdf_v2"
        },
        "security": [
          {
            "BearerAuth": []
          },
          {
            "OAuth2AuthorizationCode": [
              "mcp:read"
            ]
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "positions"
                ],
                "properties": {
                  "positions": {
                    "type": "array",
                    "minItems": 1,
                    "maxItems": 100,
                    "items": {
                      "type": "object",
                      "required": [
                        "ticker",
                        "weight"
                      ],
                      "properties": {
                        "ticker": {
                          "type": "string"
                        },
                        "weight": {
                          "type": "number",
                          "format": "float",
                          "exclusiveMinimum": 0
                        }
                      }
                    }
                  },
                  "title": {
                    "type": "string",
                    "maxLength": 200,
                    "description": "Optional report title."
                  },
                  "as_of_date": {
                    "type": "string",
                    "format": "date",
                    "description": "Optional label date (YYYY-MM-DD); data is still latest available from V3."
                  },
                  "format": {
                    "type": "string",
                    "enum": [
                      "pdf",
                      "json",
                      "png"
                    ],
                    "default": "json"
                  },
                  "include_diversification": {
                    "type": "boolean",
                    "default": false,
                    "description": "When true, adds a `diversification` block to portfolio_risk_index with correlation-adjusted ER, credits, and chart-friendly layers[]."
                  },
                  "window_days": {
                    "type": "integer",
                    "minimum": 20,
                    "maximum": 2000,
                    "default": 252,
                    "description": "Rolling window (trading days) for ETF return correlations. Only used when include_diversification=true."
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "JSON snapshot or PDF bytes.",
            "headers": {
              "X-API-Cost-USD": {
                "schema": {
                  "type": "string"
                }
              },
              "X-Cache": {
                "schema": {
                  "type": "string"
                },
                "description": "MISS on first generation; HIT when served from 24h cache."
              }
            },
            "content": {
              "application/pdf": {
                "schema": {
                  "type": "string",
                  "format": "binary"
                }
              },
              "application/json": {
                "schema": {
                  "type": "object",
                  "additionalProperties": true
                }
              }
            }
          },
          "400": {
            "description": "Invalid body.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "402": {
            "description": "Insufficient balance.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/InsufficientBalance"
                }
              }
            }
          },
          "501": {
            "description": "format=png not supported.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/snapshot": {
      "post": {
        "summary": "Canonical JSON snapshot (portfolio or single ticker)",
        "description": "Per Snapshot Architecture v3, this is the canonical public analysis interface. One structured response shape (`snapshot / time_behavior / attribution / risk_summary`) for either a weighted portfolio (`type: \"portfolio\"`) or a single name (`type: \"ticker\"`): L3 explained-risk decomposition, L3 hedge ratios, frozen-weight daily return attribution (gross, market, sector/subsector strips, residual), cumulative return and drawdown over `lookback_days` trading days, and risk summary.\nFor `type: \"ticker\"` the response also includes `snapshot.ticker_meta` with sector/subsector ETFs and the active L3 factor list. Internal endpoints `/decompose`, `/metrics/{ticker}`, `/portfolio/risk-snapshot`, `/batch/analyze` remain callable but are not the recommended public surface; new clients should use `/snapshot`.\n**Billing:** `portfolio-risk-snapshot` capability ($1.25 per request, single bundled charge).\n**Compact response:** `?compact=1` or `Prefer: return=minimal` omits `time_behavior` and `attribution` (large daily arrays); keeps `snapshot`, `risk_summary`, and `_metadata`.\n**Idempotency:** Optional `Idempotency-Key` header (POST). Repeating the same key with the same JSON body within 24h returns the cached response without a second charge when Redis is configured (`X-Idempotent-Replayed: true`).\n",
        "operationId": "postCanonicalSnapshot",
        "tags": [
          "Risk Metrics"
        ],
        "x-pricing": {
          "capability_id": "portfolio-risk-snapshot",
          "tier": "premium",
          "model": "per_request",
          "cost_usd": 1.25,
          "currency": "USD",
          "billing_code": "risk_snapshot_pdf_v2"
        },
        "security": [
          {
            "BearerAuth": []
          },
          {
            "OAuth2AuthorizationCode": [
              "mcp:read"
            ]
          }
        ],
        "parameters": [
          {
            "name": "compact",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "0",
                "1",
                "true",
                "false",
                "yes",
                "no"
              ]
            },
            "description": "When `1`, `true`, or `yes`, returns a smaller payload (drops time series blocks)."
          },
          {
            "name": "Prefer",
            "in": "header",
            "required": false,
            "schema": {
              "type": "string"
            },
            "description": "Include `return=minimal` for the same effect as `compact=1`."
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "schema": {
              "type": "string",
              "maxLength": 256
            },
            "description": "Optional. Dedupes identical POST bodies per user for 24h; second response includes `X-Idempotent-Replayed: true`. Requires Upstash Redis (`UPSTASH_REDIS_REST_URL` / `UPSTASH_REDIS_REST_TOKEN`).\n"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "oneOf": [
                  {
                    "$ref": "#/components/schemas/CanonicalSnapshotPortfolioRequest"
                  },
                  {
                    "$ref": "#/components/schemas/CanonicalSnapshotTickerRequest"
                  }
                ],
                "discriminator": {
                  "propertyName": "type",
                  "mapping": {
                    "portfolio": "#/components/schemas/CanonicalSnapshotPortfolioRequest",
                    "ticker": "#/components/schemas/CanonicalSnapshotTickerRequest"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Canonical snapshot JSON (includes `_metadata` and `_agent` on the wire).",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/CanonicalSnapshotResponse"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "_metadata": {
                          "type": "object",
                          "additionalProperties": true
                        },
                        "_agent": {
                          "type": "object",
                          "additionalProperties": true
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "400": {
            "description": "Invalid body, mixed weight/shares styles, or unsupported `type`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "402": {
            "description": "Insufficient balance.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/InsufficientBalance"
                }
              }
            }
          }
        }
      }
    },
    "/snapshot/{entity_kind}/{id}/panels/{slug}": {
      "get": {
        "summary": "Snapshot panel drill-down (artifact PNG/JSON)",
        "description": "Addressable panel from the Artifact Registry (O.6). Product alias onto render-svc `POST /artifacts/render`. Stock panels: `l3_explained_risk_hbar`, `hedge_notionals_hbar`, `hedge_depth_retained`, `watchlist_er_stacked` (pass `tickers=CRM,MSFT,NVDA`), and `_full` (composed DD page from GCS). Institutional DD figure units (`dd_peer_dna`, more to follow) are batch pre-rendered for a hot ticker cohort — same pixels as the institutional letter page; outside the cohort the route returns 501 with a pointer to `_full` and a request path (email service@riskmodels.app to have a ticker added). See BWMACRO `docs/architecture/SNAPSHOT_CANONICAL_PROCESS_ADR.md`.\n**Billing:** `portfolio-risk-snapshot` ($1.25; cache hits may be $0).\n",
        "operationId": "getSnapshotPanel",
        "tags": [
          "Risk Metrics"
        ],
        "x-pricing": {
          "capability_id": "portfolio-risk-snapshot",
          "tier": "premium",
          "model": "per_request",
          "cost_usd": 1.25,
          "currency": "USD",
          "billing_code": "risk_snapshot_pdf_v2"
        },
        "security": [
          {
            "BearerAuth": []
          },
          {
            "OAuth2AuthorizationCode": [
              "mcp:read"
            ]
          }
        ],
        "parameters": [
          {
            "name": "entity_kind",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "enum": [
                "stock",
                "fund",
                "filer_13f",
                "client_portfolio",
                "cohort"
              ]
            }
          },
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "Ticker (CRM), BW-STOCK-CRM, fund/filer id, or WATCHLIST."
          },
          {
            "name": "slug",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "Panel artifact slug or `_full`."
          },
          {
            "name": "format",
            "in": "query",
            "schema": {
              "type": "string",
              "enum": [
                "png",
                "json",
                "svg",
                "pdf"
              ],
              "default": "png"
            }
          },
          {
            "name": "version",
            "in": "query",
            "schema": {
              "type": "string",
              "default": "v1"
            }
          },
          {
            "name": "as_of",
            "in": "query",
            "schema": {
              "type": "string",
              "default": "latest"
            }
          },
          {
            "name": "tickers",
            "in": "query",
            "schema": {
              "type": "string"
            },
            "description": "Comma-separated tickers for watchlist_er_stacked."
          }
        ],
        "responses": {
          "200": {
            "description": "Panel PNG/SVG bytes or JSON contract.",
            "content": {
              "image/png": {
                "schema": {
                  "type": "string",
                  "format": "binary"
                }
              },
              "application/json": {
                "schema": {
                  "type": "object",
                  "additionalProperties": true
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "404": {
            "description": "Unknown panel or missing DD asset.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "501": {
            "description": "render-svc not configured or panel not live-renderable.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/metrics/{ticker}/snapshot.pdf": {
      "get": {
        "summary": "Single-ticker risk snapshot PDF",
        "description": "Convenience endpoint for one-name PDF snapshot (implicit weight 1.0). Same capability and $1.25 pricing as `POST /portfolio/risk-snapshot` with `format=pdf`. Cached 24h per authenticated user and ticker.\n**Billing:** $1.25 per successful response (not charged on cache hit).\n",
        "operationId": "getMetricsTickerSnapshotPdf",
        "tags": [
          "Risk Metrics"
        ],
        "x-pricing": {
          "capability_id": "portfolio-risk-snapshot",
          "tier": "premium",
          "model": "per_request",
          "cost_usd": 1.25,
          "currency": "USD",
          "billing_code": "risk_snapshot_pdf_v2"
        },
        "security": [
          {
            "BearerAuth": []
          },
          {
            "OAuth2AuthorizationCode": [
              "mcp:read"
            ]
          }
        ],
        "parameters": [
          {
            "name": "ticker",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "US equity ticker (case-insensitive)."
          }
        ],
        "responses": {
          "200": {
            "description": "PDF snapshot.",
            "headers": {
              "X-API-Cost-USD": {
                "schema": {
                  "type": "string"
                }
              },
              "X-Cache": {
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/pdf": {
                "schema": {
                  "type": "string",
                  "format": "binary"
                }
              }
            }
          },
          "400": {
            "description": "Invalid ticker.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "402": {
            "description": "Insufficient balance.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/InsufficientBalance"
                }
              }
            }
          },
          "404": {
            "description": "Ticker could not be resolved."
          }
        }
      }
    },
    "/webhooks/subscribe": {
      "get": {
        "summary": "List webhook subscriptions",
        "description": "Returns active webhook subscriptions for the authenticated user (secrets omitted).",
        "operationId": "listWebhookSubscriptions",
        "tags": [
          "Webhooks"
        ],
        "responses": {
          "200": {
            "description": "Subscription list.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "subscriptions": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "id": {
                            "type": "string",
                            "format": "uuid"
                          },
                          "url": {
                            "type": "string",
                            "format": "uri"
                          },
                          "events": {
                            "type": "array",
                            "items": {
                              "type": "string",
                              "enum": [
                                "batch.completed"
                              ]
                            }
                          },
                          "active": {
                            "type": "boolean"
                          },
                          "created_at": {
                            "type": "string",
                            "format": "date-time"
                          },
                          "updated_at": {
                            "type": "string",
                            "format": "date-time"
                          }
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "500": {
            "description": "Server error."
          }
        }
      },
      "post": {
        "summary": "Create webhook subscription",
        "description": "Register an HTTPS URL for outbound events (e.g. `batch.completed`). The `secret` is returned once in the response; store it to verify `X-RiskModels-Signature` (HMAC-SHA256).\n",
        "operationId": "createWebhookSubscription",
        "tags": [
          "Webhooks"
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "url",
                  "events"
                ],
                "properties": {
                  "url": {
                    "type": "string",
                    "format": "uri",
                    "maxLength": 2048,
                    "description": "Must use https://"
                  },
                  "events": {
                    "type": "array",
                    "minItems": 1,
                    "items": {
                      "type": "string",
                      "enum": [
                        "batch.completed"
                      ]
                    }
                  },
                  "active": {
                    "type": "boolean",
                    "default": true
                  },
                  "secret": {
                    "type": "string",
                    "minLength": 24,
                    "maxLength": 512,
                    "description": "Optional custom secret; otherwise server-generated."
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Created; `secret` included in body for verification.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "subscription": {
                      "type": "object",
                      "properties": {
                        "id": {
                          "type": "string",
                          "format": "uuid"
                        },
                        "url": {
                          "type": "string",
                          "format": "uri"
                        },
                        "events": {
                          "type": "array",
                          "items": {
                            "type": "string"
                          }
                        },
                        "active": {
                          "type": "boolean"
                        },
                        "created_at": {
                          "type": "string",
                          "format": "date-time"
                        },
                        "secret": {
                          "type": "string"
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Invalid URL or body."
          },
          "401": {
            "description": "Unauthorized."
          },
          "500": {
            "description": "Server error."
          }
        }
      },
      "delete": {
        "summary": "Delete webhook subscription",
        "description": "Remove a subscription by id (`?id=` query parameter).",
        "operationId": "deleteWebhookSubscription",
        "tags": [
          "Webhooks"
        ],
        "parameters": [
          {
            "name": "id",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Deleted.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "ok": {
                      "type": "boolean",
                      "example": true
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Missing id."
          },
          "401": {
            "description": "Unauthorized."
          },
          "404": {
            "description": "Subscription not found."
          }
        }
      }
    },
    "/peers": {
      "get": {
        "summary": "Sector / subsector peer cohort",
        "description": "Resolve a ticker's peer cohort from the symbols registry, ordered by latest market cap. Defaults to `subsector_etf` grouping and falls back to `sector_etf` when the subsector cohort is thin. Powers PeerGroupProxy selection-skill workflows without exposing storage credentials to clients.\n**Authentication:** Required (API Key or OAuth2)\n**Billing:** $0.005 per request\n",
        "operationId": "getPeers",
        "tags": [
          "Utility"
        ],
        "x-pricing": {
          "capability_id": "peers",
          "tier": "baseline",
          "model": "per_request",
          "cost_usd": 0.005,
          "currency": "USD",
          "billing_code": "peers_v2"
        },
        "security": [
          {
            "BearerAuth": []
          },
          {
            "OAuth2AuthorizationCode": [
              "mcp:read"
            ]
          }
        ],
        "parameters": [
          {
            "name": "ticker",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "Target ticker (case-insensitive).",
            "example": "NVDA"
          },
          {
            "name": "group_by",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "subsector_etf",
                "sector_etf"
              ],
              "default": "subsector_etf"
            },
            "description": "Peer grouping field."
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 200,
              "default": 50
            },
            "description": "Maximum peers to return (excluding the target)."
          }
        ],
        "responses": {
          "200": {
            "description": "Peer cohort for the ticker.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PeersResponse"
                }
              }
            }
          },
          "400": {
            "description": "Missing or invalid query parameters.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid Bearer token.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "404": {
            "description": "Ticker not found.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/artifacts/capability": {
      "get": {
        "summary": "Artifact render capability",
        "description": "The verified (slug, subject_kind) pairs render-svc can serve, each slug's applicable render params, and the pairs that were measured and refused.\nDerived from the audited capability table on every request, so a pair that was never verified cannot be advertised. Answers \"what renders for this subject?\" before a client mounts anything — previously the only way to learn the answer was to request something that failed, because the reconciled table appeared solely inside error payloads.\n**Authentication:** None. Discovery only: it names renderers and subject kinds and returns no subject content, no holdings and no numbers.\n**Billing:** Free.\n",
        "operationId": "getArtifactCapability",
        "tags": [
          "Discovery"
        ],
        "x-pricing": {
          "capability_id": "artifact-capability",
          "tier": "baseline",
          "model": "per_request",
          "cost_usd": 0,
          "currency": "USD",
          "billing_code": "artifact_capability"
        },
        "security": [],
        "parameters": [
          {
            "name": "subject_kind",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "fund",
                "etf",
                "filer_13f",
                "cohort",
                "stock",
                "client_portfolio"
              ]
            },
            "description": "Narrow `pairs` to one subject kind. `unavailable` is never narrowed: a caller needs to tell \"measured and refused\" from \"never asked\".\n",
            "example": "fund"
          },
          {
            "name": "slug",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            },
            "description": "Narrow `pairs` to one artifact slug.",
            "example": "cumulative_return_strip"
          }
        ],
        "responses": {
          "200": {
            "description": "Capability document. A narrowing query that matches nothing returns 200 with an empty `pairs` list, not 404.\n",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ArtifactCapabilityResponse"
                }
              }
            }
          },
          "400": {
            "description": "Unknown subject_kind.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/artifacts/as-of": {
      "get": {
        "summary": "Artifact pre-rendered vintages",
        "description": "The pre-rendered `as_of` dates for one (slug, subject_id), with the formats stored under each date and their object URIs. Read-only: no render is triggered by asking.\nPre-rendered subject kinds (filers, cohorts) have no live loader, so a caller must know which dates exist. For cohort subjects `as_of=latest` on the render call resolves to the newest date listed here; for filers pass one of these dates explicitly. Both spellings of a filer id are searched and merged.\n**Authentication:** API key or session. Unmetered.\n**Billing:** Free.\n",
        "operationId": "getArtifactAsOf",
        "tags": [
          "Discovery"
        ],
        "x-pricing": {
          "capability_id": "artifact-as-of",
          "tier": "baseline",
          "model": "per_request",
          "cost_usd": 0,
          "currency": "USD",
          "billing_code": "artifact_as_of_v1"
        },
        "parameters": [
          {
            "name": "slug",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string",
              "pattern": "^[a-z][a-z0-9_]*$"
            },
            "description": "Artifact slug.",
            "example": "risk_dna_stacked"
          },
          {
            "name": "subject_id",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "Subject id with a known prefix (BW-COHORT-, BW-FILER-, BW-FUND-, BW-STOCK-, BW-PORTFOLIO-, BW-ETF-).\n",
            "example": "BW-COHORT-RES-MAG7"
          },
          {
            "name": "version",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "pattern": "^v\\d+$",
              "default": "v1"
            },
            "description": "Artifact version tag."
          }
        ],
        "responses": {
          "200": {
            "description": "At least one vintage is pre-rendered.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ArtifactAsOfResponse"
                }
              }
            }
          },
          "400": {
            "description": "Missing or malformed slug, subject_id, or version.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid Bearer token.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "404": {
            "description": "Nothing pre-rendered. `slug_populated` in the body distinguishes an unbuilt slug (false) from an unknown subject on a populated slug (true).\n",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/tickers": {
      "get": {
        "summary": "Ticker universe search",
        "description": "List tickers in the universe or search by name/symbol. Free (discovery). Use the `mag7` flag to retrieve the MAG7 tickers. Use `include_metadata` for sector and ETF assignment per ticker.\n",
        "operationId": "getTickers",
        "tags": [
          "Utility"
        ],
        "x-pricing": {
          "capability_id": "ticker-search",
          "tier": "baseline",
          "model": "per_request",
          "cost_usd": 0,
          "currency": "USD",
          "billing_code": "ticker_search_v2"
        },
        "security": [],
        "parameters": [
          {
            "name": "array",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "ticker",
                "teo"
              ]
            },
            "description": "`ticker` returns all ticker symbols. `teo` returns valid trading dates."
          },
          {
            "name": "search",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            },
            "description": "Search string matched against ticker symbol or company name.",
            "example": "NVDA"
          },
          {
            "name": "mag7",
            "in": "query",
            "required": false,
            "schema": {
              "type": "boolean"
            },
            "description": "Return only MAG7 tickers (AAPL, MSFT, NVDA, GOOGL, AMZN, META, TSLA)."
          },
          {
            "name": "include_metadata",
            "in": "query",
            "required": false,
            "schema": {
              "type": "boolean"
            },
            "description": "Include sector, sector ETF, and subsector ETF per ticker."
          }
        ],
        "responses": {
          "200": {
            "description": "Ticker list or search results.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "tickers": {
                      "type": "array",
                      "items": {
                        "type": "string"
                      },
                      "example": [
                        "AAPL",
                        "MSFT",
                        "NVDA"
                      ]
                    },
                    "metadata": {
                      "type": "object",
                      "nullable": true,
                      "additionalProperties": {
                        "type": "object",
                        "properties": {
                          "name": {
                            "type": "string"
                          },
                          "sector": {
                            "type": "string"
                          },
                          "sector_etf": {
                            "type": "string"
                          },
                          "subsector_etf": {
                            "type": "string"
                          }
                        }
                      }
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/estimate": {
      "post": {
        "summary": "Estimate request cost",
        "description": "Returns predicted cost before a request is made. Free to call, requires authentication. Used by AI agents for pre-flight cost checks.\n",
        "operationId": "estimateCost",
        "tags": [
          "Billing"
        ],
        "x-pricing": {
          "metered": false,
          "cost_usd": 0,
          "note": "Pre-flight cost estimate; not deducted from balance."
        },
        "security": [
          {
            "BearerAuth": []
          },
          {
            "OAuth2AuthorizationCode": [
              "mcp:read"
            ]
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "endpoint"
                ],
                "properties": {
                  "endpoint": {
                    "type": "string",
                    "example": "ticker-returns",
                    "description": "Target endpoint (ticker-returns, batch-analyze, l3-decomposition, etc.)"
                  },
                  "params": {
                    "type": "object",
                    "description": "Same params as the target endpoint",
                    "example": {
                      "ticker": "AAPL",
                      "years": 5
                    }
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Cost estimate",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/EstimateResponse"
                }
              }
            }
          },
          "400": {
            "description": "Unknown endpoint or invalid request"
          },
          "401": {
            "description": "Authentication required",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded.",
            "headers": {
              "Retry-After": {
                "schema": {
                  "type": "integer"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/RateLimitExceeded"
                }
              }
            }
          }
        }
      }
    },
    "/health": {
      "get": {
        "summary": "Service health check",
        "description": "Returns current service status, version, capability availability (from recent billing_events), latest-session gross-return coverage (completeness signal), and optional macro_factors freshness for macro factor APIs (see macro_factors in the response). Free, no auth required.\n",
        "operationId": "getHealth",
        "tags": [
          "Utility"
        ],
        "x-pricing": {
          "capability_id": "health-status",
          "tier": "baseline",
          "model": "per_request",
          "cost_usd": 0,
          "currency": "USD",
          "billing_code": "health_check"
        },
        "security": [],
        "responses": {
          "200": {
            "description": "Service is up.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "status": {
                      "type": "string",
                      "enum": [
                        "healthy",
                        "degraded",
                        "down"
                      ]
                    },
                    "timestamp": {
                      "type": "string",
                      "format": "date-time"
                    },
                    "version": {
                      "type": "string",
                      "example": "3.0.0-agent"
                    },
                    "services": {
                      "type": "object",
                      "additionalProperties": true
                    },
                    "capabilities": {
                      "type": "object",
                      "additionalProperties": true
                    },
                    "teo_coverage": {
                      "type": "object",
                      "description": "Coverage of returns_gross at the newest teo in security_history vs stock universe; sparse values suggest the latest session is still backfilling (10% threshold).",
                      "properties": {
                        "latest_teo": {
                          "type": "string",
                          "format": "date",
                          "nullable": true
                        },
                        "universe_stock_count": {
                          "type": "integer"
                        },
                        "non_null_returns_symbol_count": {
                          "type": "integer"
                        },
                        "latest_teo_coverage_pct": {
                          "type": "number",
                          "nullable": true,
                          "description": "Percent 0–100 of universe with non-null gross return at latest_teo."
                        },
                        "latest_session_returns_pending": {
                          "type": "boolean"
                        },
                        "query_error": {
                          "type": "string"
                        }
                      }
                    },
                    "macro_factors": {
                      "type": "object",
                      "nullable": true,
                      "description": "Daily macro factor return series freshness in Supabase macro_factors (POST /correlation, GET /metrics/{ticker}/correlation, GET /macro-factors). Omitted or partial if the table is empty or the query fails.\n",
                      "properties": {
                        "status": {
                          "type": "string",
                          "enum": [
                            "healthy",
                            "stale",
                            "unavailable"
                          ]
                        },
                        "latest_teos": {
                          "type": "object",
                          "additionalProperties": {
                            "type": "string",
                            "nullable": true
                          },
                          "description": "Most recent teo observed per factor_key (e.g. bitcoin, vix)."
                        },
                        "row_count_last_7d": {
                          "type": "integer"
                        },
                        "newest_teo": {
                          "type": "string",
                          "format": "date",
                          "nullable": true
                        },
                        "oldest_teo": {
                          "type": "string",
                          "format": "date",
                          "nullable": true
                        },
                        "stale": {
                          "type": "boolean",
                          "description": "True when newest_teo is older than roughly three trading days."
                        }
                      }
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/status": {
      "get": {
        "summary": "Aggregate service reliability metrics",
        "description": "Measured latency percentiles (p50/p95/p99) and a 5xx-only success rate over a window, aggregated across all capabilities from request telemetry, plus a per-capability breakdown. Numbers are observed from real traffic, not asserted SLAs. Metered 4xx (auth/payment/rate-limit) are excluded from both latency and success_rate. Free, no auth required.\n",
        "operationId": "getStatus",
        "tags": [
          "Utility"
        ],
        "parameters": [
          {
            "name": "window_hours",
            "in": "query",
            "required": false,
            "description": "Lookback window in hours (default 24, max 720).",
            "schema": {
              "type": "integer",
              "default": 24,
              "minimum": 1,
              "maximum": 720
            }
          }
        ],
        "x-pricing": {
          "capability_id": "status-metrics",
          "tier": "baseline",
          "model": "per_request",
          "cost_usd": 0,
          "currency": "USD",
          "billing_code": "status_metrics"
        },
        "security": [],
        "responses": {
          "200": {
            "description": "Reliability metrics for the window.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "window_hours": {
                      "type": "integer"
                    },
                    "measured_through": {
                      "type": "string",
                      "format": "date-time"
                    },
                    "sample_size": {
                      "type": "integer",
                      "description": "Service-relevant events in window (4xx excluded)."
                    },
                    "latency_ms": {
                      "type": "object",
                      "nullable": true,
                      "description": "Server-measured processing latency (excludes network); null if no traffic.",
                      "properties": {
                        "p50": {
                          "type": "number"
                        },
                        "p95": {
                          "type": "number"
                        },
                        "p99": {
                          "type": "number"
                        },
                        "avg": {
                          "type": "number"
                        }
                      }
                    },
                    "success_rate": {
                      "type": "number",
                      "nullable": true,
                      "description": "Fraction of service-relevant events that did not 5xx; null if no traffic."
                    },
                    "by_capability": {
                      "type": "object",
                      "additionalProperties": true
                    },
                    "note": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/feedback": {
      "post": {
        "summary": "Submit feedback on an API result",
        "description": "Flag a specific result by its _agent.request_id with a rating / category / correction note. Trust-loop signal for offline data-quality and docs triage; no output changes at request time. Authenticated, free (no metering). Provide at least one of rating, category, or comment.\n",
        "operationId": "postFeedback",
        "tags": [
          "Utility"
        ],
        "x-pricing": {
          "capability_id": "feedback",
          "tier": "baseline",
          "model": "per_request",
          "cost_usd": 0,
          "currency": "USD",
          "billing_code": "feedback"
        },
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "request_id": {
                    "type": "string",
                    "description": "The _agent.request_id / X-Request-ID of the call being rated. Nullable for general feedback."
                  },
                  "capability_id": {
                    "type": "string"
                  },
                  "rating": {
                    "type": "string",
                    "enum": [
                      "up",
                      "down"
                    ]
                  },
                  "category": {
                    "type": "string",
                    "enum": [
                      "data_quality",
                      "incorrect_result",
                      "latency",
                      "docs",
                      "feature_request",
                      "other"
                    ]
                  },
                  "comment": {
                    "type": "string",
                    "maxLength": 4000
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Feedback recorded.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "ok": {
                      "type": "boolean"
                    },
                    "feedback_id": {
                      "type": "string"
                    },
                    "message": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Invalid or empty feedback (no rating/category/comment, bad enum, or comment too long)."
          },
          "401": {
            "description": "Missing or invalid API key."
          },
          "503": {
            "description": "Feedback store temporarily unavailable (retry)."
          }
        }
      }
    },
    "/balance": {
      "get": {
        "summary": "Account balance and rate limits",
        "description": "Returns current prepaid balance, account status, and rate-limit settings for the authenticated token.",
        "operationId": "getBalance",
        "tags": [
          "Account"
        ],
        "responses": {
          "200": {
            "description": "Account balance and status.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "email": {
                      "type": "string",
                      "example": "user@example.com"
                    },
                    "balance_usd": {
                      "type": "number",
                      "format": "float",
                      "example": 24.85
                    },
                    "currency": {
                      "type": "string",
                      "example": "USD"
                    },
                    "account_type": {
                      "type": "string",
                      "example": "pay_as_you_go"
                    },
                    "status": {
                      "type": "object",
                      "properties": {
                        "account": {
                          "type": "string",
                          "example": "active"
                        },
                        "billing": {
                          "type": "string",
                          "example": "ok"
                        },
                        "can_make_requests": {
                          "type": "boolean"
                        }
                      }
                    },
                    "limits": {
                      "type": "object",
                      "properties": {
                        "rate_limit_per_minute": {
                          "type": "integer",
                          "example": 60
                        },
                        "daily_request_limit": {
                          "type": "integer",
                          "nullable": true
                        }
                      }
                    },
                    "last_updated": {
                      "type": "string",
                      "format": "date-time"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid Bearer token.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "402": {
            "description": "Insufficient balance.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/InsufficientBalance"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded.",
            "headers": {
              "Retry-After": {
                "schema": {
                  "type": "integer"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/RateLimitExceeded"
                }
              }
            }
          }
        }
      },
      "patch": {
        "summary": "Update auto-refill settings",
        "description": "Updates prepaid auto-refill on `agent_accounts`. When `enabled` is true, `refill_amount_usd` (20, 50, or 100) and `min_threshold_tokens` (maps to USD threshold between $5 and $50) are required.\n",
        "operationId": "patchBalanceAutoRefill",
        "tags": [
          "Account"
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "enabled"
                ],
                "properties": {
                  "enabled": {
                    "type": "boolean",
                    "description": "Enable or disable auto top-up."
                  },
                  "refill_amount_usd": {
                    "type": "number",
                    "enum": [
                      20,
                      50,
                      100
                    ],
                    "description": "Required when enabling auto-refill."
                  },
                  "min_threshold_tokens": {
                    "type": "number",
                    "description": "Token threshold; converted to USD for storage. Implied USD must be between 5 and 50 when enabling.\n"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Updated settings or current state.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "additionalProperties": true
                }
              }
            }
          },
          "400": {
            "description": "Validation error."
          },
          "401": {
            "description": "Unauthorized."
          },
          "500": {
            "description": "Server error."
          }
        }
      }
    },
    "/user/billing-config": {
      "get": {
        "summary": "Auto-refill billing preferences",
        "description": "Current auto-refill flags and amounts from `agent_accounts`. Same semantics as portal settings; use `PATCH /balance` for the alternate auto-refill field names (`enabled`, `refill_amount_usd`).\n",
        "operationId": "getUserBillingConfig",
        "tags": [
          "Account"
        ],
        "responses": {
          "200": {
            "description": "Billing preferences.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "auto_top_up": {
                      "type": "boolean"
                    },
                    "auto_top_up_amount": {
                      "type": "number"
                    },
                    "auto_top_up_threshold": {
                      "type": "number"
                    },
                    "has_payment_method": {
                      "type": "boolean"
                    },
                    "allowed_refill_amounts": {
                      "type": "array",
                      "items": {
                        "type": "number"
                      }
                    },
                    "threshold_bounds": {
                      "type": "object",
                      "properties": {
                        "min": {
                          "type": "number"
                        },
                        "max": {
                          "type": "number"
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized."
          },
          "404": {
            "description": "Agent account not found."
          },
          "500": {
            "description": "Server error."
          }
        }
      },
      "patch": {
        "summary": "Update auto-refill billing preferences",
        "description": "Partial update of `auto_top_up`, `auto_top_up_amount` (must be 20, 50, or 100), and/or `auto_top_up_threshold` (USD, 5–50). Requires a saved payment method to enable auto-refill.\n",
        "operationId": "patchUserBillingConfig",
        "tags": [
          "Account"
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "auto_top_up": {
                    "type": "boolean"
                  },
                  "auto_top_up_amount": {
                    "type": "number",
                    "enum": [
                      20,
                      50,
                      100
                    ]
                  },
                  "auto_top_up_threshold": {
                    "type": "number",
                    "minimum": 5,
                    "maximum": 50
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Updated fields.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "success": {
                      "type": "boolean"
                    },
                    "auto_top_up": {
                      "type": "boolean"
                    },
                    "auto_top_up_amount": {
                      "type": "number"
                    },
                    "auto_top_up_threshold": {
                      "type": "number"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation or missing payment method."
          },
          "401": {
            "description": "Unauthorized."
          },
          "500": {
            "description": "Server error."
          }
        }
      }
    },
    "/invoices": {
      "get": {
        "summary": "Invoice history and spend summary",
        "description": "Returns paginated invoice history and a summary of spend by period (month/quarter/year).",
        "operationId": "getInvoices",
        "tags": [
          "Account"
        ],
        "parameters": [
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "default": 20
            },
            "description": "Maximum number of invoices to return."
          },
          {
            "name": "period",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "month",
                "quarter",
                "year"
              ],
              "default": "month"
            },
            "description": "Aggregation period for spend summary."
          }
        ],
        "responses": {
          "200": {
            "description": "Invoice list and spend summary.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "invoices": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "status": {
                            "type": "string"
                          },
                          "amount_usd": {
                            "type": "number",
                            "format": "float"
                          }
                        }
                      }
                    },
                    "summary": {
                      "type": "object",
                      "properties": {
                        "period": {
                          "type": "string"
                        },
                        "total_invoices": {
                          "type": "integer"
                        },
                        "total_spent_usd": {
                          "type": "number",
                          "format": "float"
                        },
                        "total_requests": {
                          "type": "integer"
                        },
                        "current_period_cost_usd": {
                          "type": "number",
                          "format": "float"
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid Bearer token.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "402": {
            "description": "Insufficient balance.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/InsufficientBalance"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded.",
            "headers": {
              "Retry-After": {
                "schema": {
                  "type": "integer"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/RateLimitExceeded"
                }
              }
            }
          }
        }
      }
    },
    "/auth/provision": {
      "post": {
        "summary": "Provision an API key",
        "description": "Exchange a valid session JWT for a long-lived Bearer API key in format `rm_agent_{live|test}_{random}_{checksum}`. Intended for AI agents that need a stable key for repeated calls.\n",
        "operationId": "provisionToken",
        "tags": [
          "Authentication"
        ],
        "responses": {
          "200": {
            "description": "New API key provisioned.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "api_key": {
                      "type": "string",
                      "example": "rm_agent_live_a1b2c3d4_xyz789"
                    },
                    "environment": {
                      "type": "string",
                      "enum": [
                        "live",
                        "test"
                      ]
                    },
                    "created_at": {
                      "type": "string",
                      "format": "date-time"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid session JWT.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/auth/provision-free": {
      "post": {
        "summary": "Provision a free-tier API key",
        "description": "Generate a free API key with limited daily usage (100 req/day, 10 req/min). No payment or email required. Returns the key once — store it immediately.\n",
        "operationId": "provisionFreeToken",
        "tags": [
          "Authentication"
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "agent_name"
                ],
                "properties": {
                  "agent_name": {
                    "type": "string",
                    "minLength": 3,
                    "description": "Display name for the free-tier agent (≥ 3 characters).",
                    "example": "my-research-bot"
                  },
                  "purpose": {
                    "type": "string",
                    "description": "Optional description of intended use.",
                    "default": "development"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Free API key created.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "account": {
                      "type": "object",
                      "properties": {
                        "user_id": {
                          "type": "string"
                        },
                        "agent_name": {
                          "type": "string"
                        },
                        "tier": {
                          "type": "string",
                          "enum": [
                            "free"
                          ]
                        },
                        "limits": {
                          "type": "object",
                          "properties": {
                            "queries_per_day": {
                              "type": "integer",
                              "example": 100
                            },
                            "queries_per_minute": {
                              "type": "integer",
                              "example": 10
                            }
                          }
                        }
                      }
                    },
                    "credentials": {
                      "type": "object",
                      "properties": {
                        "api_key": {
                          "type": "string",
                          "description": "Shown only once. Store securely.",
                          "example": "rm_agent_live_abc123_xyz"
                        },
                        "prefix": {
                          "type": "string"
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Invalid agent_name.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/auth/free-tier-status": {
      "get": {
        "summary": "Free-tier usage status",
        "description": "Returns current daily usage and remaining quota for a free-tier API key. Authenticated with Bearer token. Returns HTTP 200 with `tier` field for non-free accounts.\n",
        "operationId": "getFreeTierStatus",
        "tags": [
          "Authentication"
        ],
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "Free-tier usage status.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "tier": {
                      "type": "string",
                      "example": "free"
                    },
                    "user_id": {
                      "type": "string"
                    },
                    "usage": {
                      "type": "object",
                      "properties": {
                        "queries_today": {
                          "type": "integer"
                        },
                        "queries_this_month": {
                          "type": "integer"
                        },
                        "remaining_today": {
                          "type": "integer"
                        }
                      }
                    },
                    "limits": {
                      "type": "object",
                      "properties": {
                        "queries_per_day": {
                          "type": "integer",
                          "example": 100
                        },
                        "queries_per_minute": {
                          "type": "integer",
                          "example": 10
                        }
                      }
                    },
                    "reset_date": {
                      "type": "string",
                      "format": "date-time"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid Bearer token.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "404": {
            "description": "No account found for this API key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/cli/query": {
      "post": {
        "summary": "Raw SQL query (SELECT only)",
        "description": "Execute a read-only SQL SELECT against the RiskModels database. Only SELECT statements are permitted; DML and DDL are rejected. Results capped at 10,000 rows. Billed as `cli-query` capability. Requires valid Bearer token with billing balance.\n",
        "operationId": "cliQuery",
        "tags": [
          "Utility"
        ],
        "x-pricing": {
          "capability_id": "cli-query",
          "tier": "baseline",
          "model": "per_request",
          "cost_usd": 0.015,
          "currency": "USD",
          "billing_code": "cli_query_v2"
        },
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "sql"
                ],
                "properties": {
                  "sql": {
                    "type": "string",
                    "description": "SELECT statement to execute. Multi-statement and DML/DDL are rejected.",
                    "example": "SELECT ticker, symbol FROM symbols LIMIT 10"
                  },
                  "limit": {
                    "type": "integer",
                    "description": "Max rows to return if no LIMIT clause in sql (default 100, max 10000).",
                    "default": 100
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Query results.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "results": {
                      "type": "array",
                      "items": {
                        "type": "object"
                      }
                    },
                    "count": {
                      "type": "integer"
                    },
                    "sql": {
                      "type": "string",
                      "description": "Executed SQL (may have LIMIT appended)."
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Invalid or disallowed SQL.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid Bearer token.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "402": {
            "description": "Insufficient balance.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/InsufficientBalance"
                }
              }
            }
          }
        }
      }
    },
    "/telemetry": {
      "get": {
        "summary": "Telemetry metrics",
        "description": "Performance and reliability metrics for API capabilities. Optional filter by capability and number of days. Cost: $0.01/call.\n",
        "operationId": "getTelemetry",
        "tags": [
          "Utility"
        ],
        "x-pricing": {
          "capability_id": "telemetry-metrics",
          "tier": "baseline",
          "model": "per_request",
          "cost_usd": 0.01,
          "currency": "USD",
          "billing_code": "telemetry_v3"
        },
        "parameters": [
          {
            "name": "capability",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            },
            "description": "Specific capability id to get metrics for."
          },
          {
            "name": "days",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "default": 30,
              "minimum": 1,
              "maximum": 90
            },
            "description": "Number of days of historical data."
          }
        ],
        "responses": {
          "200": {
            "description": "Telemetry metrics for the requested period."
          },
          "401": {
            "description": "Missing or invalid Bearer token.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "402": {
            "description": "Insufficient balance.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/InsufficientBalance"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded.",
            "headers": {
              "Retry-After": {
                "schema": {
                  "type": "integer"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/RateLimitExceeded"
                }
              }
            }
          }
        }
      }
    },
    "/chat": {
      "post": {
        "summary": "AI Risk Analyst",
        "description": "Natural language risk analysis with OpenAI tool calling against RiskModels data (DAL). LLM tokens are billed per the chat capability; each internal tool call is billed separately at the same rates as the corresponding REST endpoints (e.g. metrics-snapshot, ticker-returns). search_tickers is free. Returns tool_calls_summary and expanded _agent (llm_cost_usd, tool_cost_usd, tool_calls).\n",
        "operationId": "postChat",
        "tags": [
          "Risk Metrics"
        ],
        "x-pricing": {
          "capability_id": "chat-risk-analyst",
          "tier": "premium",
          "model": "per_token",
          "currency": "USD",
          "billing_code": "chat_risk_analyst_v3",
          "input_cost_per_1k": 0.005,
          "output_cost_per_1k": 0.01
        },
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "messages": {
                    "type": "array",
                    "items": {
                      "type": "object",
                      "required": [
                        "role",
                        "content"
                      ],
                      "properties": {
                        "role": {
                          "type": "string",
                          "enum": [
                            "user",
                            "assistant"
                          ]
                        },
                        "content": {
                          "type": "string"
                        }
                      }
                    }
                  },
                  "model": {
                    "type": "string",
                    "description": "AI model to use (default gpt-4o-mini)"
                  },
                  "response_mode": {
                    "type": "string",
                    "enum": [
                      "markdown",
                      "catalog",
                      "hybrid"
                    ],
                    "description": "Reserved for future streaming / A2UI modes; non-streaming JSON today."
                  },
                  "parallel_tool_calls": {
                    "type": "boolean",
                    "description": "When false, sets OpenAI parallel_tool_calls to false. Omit or true for parallel tool execution on supported models (e.g. gpt-4o-mini).\n"
                  },
                  "execute_tools_sequentially": {
                    "type": "boolean",
                    "description": "When true, server executes tool calls sequentially instead of concurrently."
                  },
                  "workspace_tools": {
                    "type": "boolean",
                    "description": "Offer the riskmodels.net workspace command-bus tools (set_subject / set_window) and emit their typed actions as distinct `action` SSE frames. Honored only when streaming (Accept: text/event-stream) — the frame is the delivery channel — and intended for the workspace-mounted portal chat, which validates actions client-side (fail-closed) before applying them.\n"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Assistant reply, accumulated token usage across tool rounds, optional tool_calls_summary, lineage metadata, and _agent cost breakdown (LLM + tools).\n",
            "headers": {
              "X-API-Cost-USD": {
                "schema": {
                  "type": "string"
                }
              },
              "X-Data-Fetch-Latency-Ms": {
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "message": {
                      "type": "object",
                      "required": [
                        "role",
                        "content"
                      ],
                      "properties": {
                        "role": {
                          "type": "string",
                          "enum": [
                            "assistant"
                          ]
                        },
                        "content": {
                          "type": "string"
                        }
                      }
                    },
                    "model": {
                      "type": "string"
                    },
                    "usage": {
                      "type": "object",
                      "nullable": true,
                      "properties": {
                        "prompt_tokens": {
                          "type": "integer"
                        },
                        "completion_tokens": {
                          "type": "integer"
                        },
                        "total_tokens": {
                          "type": "integer"
                        }
                      }
                    },
                    "tool_calls_summary": {
                      "type": "array",
                      "nullable": true,
                      "items": {
                        "$ref": "#/components/schemas/ChatToolCallSummary"
                      }
                    },
                    "_metadata": {
                      "type": "object",
                      "additionalProperties": true
                    },
                    "_agent": {
                      "$ref": "#/components/schemas/AgentMeta"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Invalid request body.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid Bearer token.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "402": {
            "description": "Insufficient balance.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/InsufficientBalance"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded.",
            "headers": {
              "Retry-After": {
                "schema": {
                  "type": "integer"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/RateLimitExceeded"
                }
              }
            }
          },
          "502": {
            "description": "Upstream AI provider error."
          },
          "503": {
            "description": "Chat not configured (e.g. missing MOONSHOT_API_KEY)."
          }
        }
      }
    },
    "/pricing": {
      "get": {
        "summary": "Public capability pricing manifest",
        "description": "Machine-readable pricing for all registered capabilities (tier, model, `cost_usd`, token rates, `min_charge`, `billing_code`). Public; no authentication. Cached at the edge.\n",
        "operationId": "getPricingManifest",
        "tags": [
          "Discovery"
        ],
        "security": [],
        "responses": {
          "200": {
            "description": "Pricing manifest JSON (`version`, `currency`, `tiers`, `endpoints`, `estimate_endpoint`, `docs`).",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "additionalProperties": true
                }
              }
            }
          }
        }
      }
    },
    "/data/funds/{bw_fund_id}": {
      "get": {
        "summary": "Get fund (registry + latest snapshot)",
        "description": "Returns the registry row from `public.funds` joined with the latest knowledge-mode snapshot from `public.funds_latest`. Bitemporal lineage surfaces as response headers `X-Data-As-Of` (= `report_date`) and `X-Data-Filing-Date` (= `filing_date`).\n\nv1 returns the latest knowledge-mode answer only (no `?as_of=` / `?mode=` query params — deferred to v2 per ARCHITECTURE_FUNDS_API.md §3.5).\n",
        "operationId": "getFundDataPlane",
        "tags": [
          "Funds Data Plane"
        ],
        "security": [
          {
            "BearerAuth": []
          },
          {}
        ],
        "parameters": [
          {
            "name": "bw_fund_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "example": "BW-FUND-S000004310"
          }
        ],
        "responses": {
          "200": {
            "description": "Fund + latest snapshot.",
            "headers": {
              "X-Data-As-Of": {
                "schema": {
                  "type": "string",
                  "format": "date"
                },
                "description": "funds_latest.report_date (period end of the snapshot)."
              },
              "X-Data-Filing-Date": {
                "schema": {
                  "type": "string",
                  "format": "date"
                },
                "description": "funds_latest.filing_date (SEC acceptance date)."
              },
              "X-Risk-Model-Version": {
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/FundWithLatest"
                }
              }
            }
          },
          "404": {
            "description": "Fund not found.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/data/funds-latest/{bw_fund_id}": {
      "get": {
        "summary": "Get just the funds_latest row",
        "description": "Returns only the wide-row snapshot from `public.funds_latest`. Skips the registry join — useful when the SDK already has the registry row or only needs the metric columns.\n",
        "operationId": "getFundLatestDataPlane",
        "tags": [
          "Funds Data Plane"
        ],
        "security": [
          {
            "BearerAuth": []
          },
          {}
        ],
        "parameters": [
          {
            "name": "bw_fund_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Latest snapshot row.",
            "headers": {
              "X-Data-As-Of": {
                "schema": {
                  "type": "string",
                  "format": "date"
                }
              },
              "X-Data-Filing-Date": {
                "schema": {
                  "type": "string",
                  "format": "date"
                }
              },
              "X-Risk-Model-Version": {
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/FundLatest"
                }
              }
            }
          },
          "404": {
            "description": "No funds_latest row for this fund.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/data/funds/batch": {
      "post": {
        "summary": "Resolve multiple funds in one call",
        "description": "Multi-fund lookup. Returns a registry+latest payload keyed by `bw_fund_id`. Up to 1000 ids per request.\n",
        "operationId": "postFundsBatchDataPlane",
        "tags": [
          "Funds Data Plane"
        ],
        "security": [
          {
            "BearerAuth": []
          },
          {}
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/FundsBatchRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Lookup map.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/FundsBatchResponse"
                }
              }
            }
          },
          "400": {
            "description": "Invalid body (missing/oversized fund_ids array).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/data/funds/search": {
      "get": {
        "summary": "Search funds by ticker or name",
        "description": "Full-text ilike search over `ticker` and `fund_name`, optionally filtered by 9-box style cell (slug or canonical name) and share-class primary flag (`?primary=true` filters `primary_bw_fund_id IS NULL`).\n",
        "operationId": "getFundsSearchDataPlane",
        "tags": [
          "Funds Data Plane"
        ],
        "security": [
          {
            "BearerAuth": []
          },
          {}
        ],
        "parameters": [
          {
            "name": "q",
            "in": "query",
            "schema": {
              "type": "string"
            },
            "description": "ilike pattern on ticker / fund_name."
          },
          {
            "name": "equity_style_9box",
            "in": "query",
            "schema": {
              "type": "string"
            },
            "description": "9-box slug (\"large-blend\") or canonical name (\"Large Blend\")."
          },
          {
            "name": "primary",
            "in": "query",
            "schema": {
              "type": "boolean",
              "default": false
            },
            "description": "When true, returns only primary share-class funds (Q5 lock)."
          },
          {
            "name": "limit",
            "in": "query",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 500,
              "default": 50
            }
          },
          {
            "name": "include_inactive",
            "in": "query",
            "schema": {
              "type": "boolean",
              "default": false
            },
            "description": "When true, also returns funds that are not active for listing (lifecycle status not `active`, or last holdings report older than 400 days before the latest active report date).\n"
          },
          {
            "name": "include_etfs",
            "in": "query",
            "schema": {
              "type": "boolean",
              "default": true
            },
            "description": "When false, drops funds flagged `is_etf`."
          }
        ],
        "responses": {
          "200": {
            "description": "Up to `limit` matching funds.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/FundsSearchResponse"
                }
              }
            }
          }
        }
      }
    },
    "/data/funds/style/{slug}/members": {
      "get": {
        "summary": "List fund_ids in a 9-box style cell",
        "description": "Returns the `bw_fund_id` list for a 9-box style cell. SDK consumers typically chain this into `/api/data/funds/batch` to materialize a peer cohort's full registry+latest payload.\n",
        "operationId": "getStyleCellMembersDataPlane",
        "tags": [
          "Funds Data Plane"
        ],
        "security": [
          {
            "BearerAuth": []
          },
          {}
        ],
        "parameters": [
          {
            "name": "slug",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "enum": [
                "large-value",
                "large-blend",
                "large-growth",
                "mid-value",
                "mid-blend",
                "mid-growth",
                "small-value",
                "small-blend",
                "small-growth"
              ]
            }
          },
          {
            "name": "primary",
            "in": "query",
            "schema": {
              "type": "boolean",
              "default": false
            }
          },
          {
            "name": "limit",
            "in": "query",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 20000,
              "default": 5000
            }
          },
          {
            "name": "include_inactive",
            "in": "query",
            "schema": {
              "type": "boolean",
              "default": false
            },
            "description": "When true, also returns funds that are not active for listing (lifecycle status not `active`, or last holdings report older than 400 days before the latest active report date).\n"
          },
          {
            "name": "include_etfs",
            "in": "query",
            "schema": {
              "type": "boolean",
              "default": true
            },
            "description": "When false, drops funds flagged `is_etf`."
          }
        ],
        "responses": {
          "200": {
            "description": "Fund id list for the cell.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/FundsStyleMembersResponse"
                }
              }
            }
          },
          "400": {
            "description": "Invalid style slug.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/data/etf/search": {
      "get": {
        "summary": "ETF search & discovery (free)",
        "description": "Free discovery endpoint for ETFs in the canonical universe. Substring + prefix search over ticker / name in the committed catalog (`mcp/data/etf_master.json`). Symmetric to `/api/data/funds/search` and `/api/13f/filers/search`. Empty `q` returns the universe (capped at `limit`).\n\nRanking (higher = better): 100 exact ticker; 70 ticker prefix; 50 ticker substring; 40 name word-boundary prefix; 30 name substring. Public read; soft gateway auth.\n",
        "operationId": "searchEtfs",
        "tags": [
          "Funds Data Plane"
        ],
        "security": [
          {
            "BearerAuth": []
          },
          {}
        ],
        "parameters": [
          {
            "name": "q",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            },
            "example": "SPY",
            "description": "Search query — ticker or partial name. Case-insensitive."
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 100,
              "default": 25
            }
          }
        ],
        "responses": {
          "200": {
            "description": "ETF search results (possibly empty).",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "query": {
                      "type": "string"
                    },
                    "n_results": {
                      "type": "integer"
                    },
                    "n_universe": {
                      "type": "integer",
                      "description": "Total ETFs in the catalog."
                    },
                    "schema_version": {
                      "type": "string",
                      "example": "etf-master/2.0"
                    },
                    "results": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "bw_etf_id": {
                            "type": "string",
                            "example": "BW-ETF-IVV"
                          },
                          "ticker": {
                            "type": "string",
                            "example": "IVV"
                          },
                          "name": {
                            "type": "string"
                          },
                          "score": {
                            "type": "integer",
                            "description": "Match score (higher = better)."
                          }
                        }
                      }
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/data/etf/{ticker}": {
      "get": {
        "summary": "ETF base metrics (canonical PortfolioSurface)",
        "description": "Latest knowledge-mode metrics for a single ETF — registry-level metadata (`portfolio_id`, `ticker`, `source_kind`, `teo_frequency`, `sponsor`) + the latest aggregate metrics from the canonical PortfolioSurface zarr (`aum_reported`, `aum_erm3`, `coverage_pct`, `n_total_holdings`, `report_date`, `availability_date`).\n\nSymmetric to `/api/data/funds/{bw_fund_id}` and `/api/13f/filers/{bw_filer_id}` — closes the \"base entity metrics\" parity gap for the ETF surface. MASTER_BACKLOG L.6 / D.9.\n\nBitemporal lineage on response headers — never collapsed: `X-Data-As-Of` (= `report_date`) and `X-Data-Availability` (= `availability_date`). No per-holding rows — call `/api/data/etf/{ticker}/holdings` for those.\n",
        "operationId": "getEtfMetricsDataPlane",
        "tags": [
          "Funds Data Plane"
        ],
        "security": [
          {
            "BearerAuth": []
          },
          {}
        ],
        "parameters": [
          {
            "name": "ticker",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "example": "IVV"
          }
        ],
        "responses": {
          "200": {
            "description": "ETF latest metrics block.",
            "headers": {
              "X-Data-As-Of": {
                "schema": {
                  "type": "string",
                  "format": "date"
                },
                "description": "report_date — the sponsor's \"Fund Holdings as of\" date."
              },
              "X-Data-Availability": {
                "schema": {
                  "type": "string",
                  "format": "date"
                },
                "description": "availability_date — when that holdings file was first observed."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "portfolio_id": {
                      "type": "string",
                      "example": "BW-ETF-IVV"
                    },
                    "ticker": {
                      "type": "string",
                      "example": "IVV"
                    },
                    "source_kind": {
                      "type": "string",
                      "enum": [
                        "etf"
                      ]
                    },
                    "teo_frequency": {
                      "type": "string",
                      "enum": [
                        "daily"
                      ]
                    },
                    "sponsor": {
                      "type": "string",
                      "nullable": true,
                      "example": "ishares"
                    },
                    "report_date": {
                      "type": "string",
                      "format": "date"
                    },
                    "availability_date": {
                      "type": "string",
                      "format": "date",
                      "nullable": true
                    },
                    "aum_reported": {
                      "type": "number",
                      "nullable": true
                    },
                    "aum_erm3": {
                      "type": "number",
                      "nullable": true
                    },
                    "coverage_pct": {
                      "type": "number",
                      "nullable": true,
                      "description": "aum_erm3 / aum_reported — in-ERM3 share of the resolved sleeve."
                    },
                    "n_total_holdings": {
                      "type": "integer"
                    }
                  }
                }
              }
            }
          },
          "404": {
            "description": "ETF not found or no holdings available.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/data/etf/{ticker}/holdings": {
      "get": {
        "summary": "ETF top-N holdings (canonical PortfolioSurface)",
        "description": "Top-N current holdings of an ETF, read from its canonical `PortfolioSurface` zarr (Funds_DAG `etf_holdings_zarr` — `source_kind=etf`, `teo_frequency=daily`). Only the in-ERM3 sleeve is materialized, so every holding carries a `bw_sym_id` (resolve to ticker/name via `/api/data/symbols/batch`). `report_date` (the sponsor's \"Fund Holdings as of\" date) and `availability_date` (when that file was first observed) are surfaced distinctly — never collapsed — and also as headers `X-Data-As-Of` (= `report_date`) and `X-Data-Availability` (= `availability_date`).\n\nv1 returns the latest snapshot only (no `?as_of=` query). See docs/architecture/CANONICAL_INTELLIGENCE_OBJECTS.md §9.\n",
        "operationId": "getEtfHoldingsDataPlane",
        "tags": [
          "Funds Data Plane"
        ],
        "security": [
          {
            "BearerAuth": []
          },
          {}
        ],
        "parameters": [
          {
            "name": "ticker",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "example": "IVV"
          },
          {
            "name": "top",
            "in": "query",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 1000,
              "default": 25
            }
          }
        ],
        "responses": {
          "200": {
            "description": "ETF holdings snapshot.",
            "headers": {
              "X-Data-As-Of": {
                "schema": {
                  "type": "string",
                  "format": "date"
                },
                "description": "report_date — the sponsor's \"Fund Holdings as of\" date."
              },
              "X-Data-Availability": {
                "schema": {
                  "type": "string",
                  "format": "date"
                },
                "description": "availability_date — when that holdings file was first observed."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "portfolio_id": {
                      "type": "string",
                      "example": "BW-ETF-IVV"
                    },
                    "ticker": {
                      "type": "string",
                      "example": "IVV"
                    },
                    "source_kind": {
                      "type": "string",
                      "enum": [
                        "etf"
                      ]
                    },
                    "teo_frequency": {
                      "type": "string",
                      "enum": [
                        "daily"
                      ]
                    },
                    "sponsor": {
                      "type": "string",
                      "nullable": true,
                      "example": "ishares"
                    },
                    "report_date": {
                      "type": "string",
                      "format": "date"
                    },
                    "availability_date": {
                      "type": "string",
                      "format": "date",
                      "nullable": true
                    },
                    "aum_reported": {
                      "type": "number",
                      "nullable": true
                    },
                    "aum_erm3": {
                      "type": "number",
                      "nullable": true
                    },
                    "coverage_pct": {
                      "type": "number",
                      "nullable": true,
                      "description": "aum_erm3 / aum_reported — in-ERM3 share of the resolved sleeve."
                    },
                    "n_holdings_returned": {
                      "type": "integer"
                    },
                    "n_total_holdings": {
                      "type": "integer"
                    },
                    "holdings": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "bw_sym_id": {
                            "type": "string"
                          },
                          "adj_mv": {
                            "type": "number"
                          },
                          "weight": {
                            "type": "number",
                            "nullable": true
                          }
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "404": {
            "description": "ETF not found or no holdings available.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/data/etf/{ticker}/portfolio": {
      "get": {
        "summary": "ETF L1/L2/L3 return decomposition time series",
        "description": "An ETF's portfolio return decomposition (Funds_DAG `surface_portfolios_zarr`) — same `(teo,)` schema as `/api/funds/{bw_fund_id}/portfolio`: `portfolio_{gross,market,sector,subsector,idiosyncratic}_return`, `identity_residual`, `weight_sum`, `n_holdings_active`, `effective_n`, `top10_weight_sum`. The response reports `weight_basis` — v1 = `latest_holdings_constant` (the factor profile of the ETF's *current* composition over ERM3 monthly's full history; the time-varying month-end basis matching the per-fund semantic is a follow-on) — and `variance_shares` (the diversification-credited full-window market/sector/subsector/residual shares). Soft gateway auth (public read).\n",
        "operationId": "getEtfPortfolioSeries",
        "tags": [
          "Funds Data Plane"
        ],
        "security": [
          {
            "BearerAuth": []
          },
          {}
        ],
        "parameters": [
          {
            "name": "ticker",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "example": "IWM"
          },
          {
            "name": "start_date",
            "in": "query",
            "schema": {
              "type": "string",
              "format": "date"
            }
          },
          {
            "name": "end_date",
            "in": "query",
            "schema": {
              "type": "string",
              "format": "date"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "ETF portfolio decomposition time series.",
            "headers": {
              "X-Data-As-Of": {
                "schema": {
                  "type": "string",
                  "format": "date"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SurfacePortfolioSeries"
                }
              }
            }
          },
          "404": {
            "description": "ETF not found or no portfolio decomposition available.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/data/benchmark/{id}": {
      "get": {
        "summary": "Benchmark surface + BenchmarkContext",
        "description": "A benchmark's canonical PortfolioSurface snapshot (Funds_DAG `benchmark_surfaces_zarr` — `source_kind=benchmark`): the `BenchmarkContext` (the immutable definition — methodology, rebalance schedule, observation semantics, `benchmark_kind`, proxy/components) plus the latest constituent weight vector (top-N). `id` accepts a `bw_bench_id` (e.g. `BW-BENCH-SPY`) or an alias (e.g. `SPY`, `70/30`). v1 catalog: SPY (index_proxy ← IVV's holdings), EQ70-30 (blend: 70% IWB + 30% IWM). `report_date` (the surface's latest teo) and `availability_date` are surfaced distinctly + as headers `X-Data-As-Of` / `X-Data-Availability`. See docs/architecture/CANONICAL_INTELLIGENCE_OBJECTS.md §9.\n",
        "operationId": "getBenchmarkSurface",
        "tags": [
          "Funds Data Plane"
        ],
        "security": [
          {
            "BearerAuth": []
          },
          {}
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "example": "SPY"
          },
          {
            "name": "top",
            "in": "query",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 1000,
              "default": 25
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Benchmark surface snapshot + context.",
            "headers": {
              "X-Data-As-Of": {
                "schema": {
                  "type": "string",
                  "format": "date"
                }
              },
              "X-Data-Availability": {
                "schema": {
                  "type": "string",
                  "format": "date"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "benchmark_context_id": {
                      "type": "string",
                      "example": "BW-BENCH-SPY"
                    },
                    "name": {
                      "type": "string"
                    },
                    "benchmark_kind": {
                      "type": "string",
                      "enum": [
                        "index_proxy",
                        "blend",
                        "peer_cohort",
                        "constructed_hedge"
                      ]
                    },
                    "source_kind": {
                      "type": "string",
                      "enum": [
                        "benchmark"
                      ]
                    },
                    "teo_frequency": {
                      "type": "string"
                    },
                    "report_date": {
                      "type": "string",
                      "format": "date"
                    },
                    "availability_date": {
                      "type": "string",
                      "format": "date",
                      "nullable": true
                    },
                    "benchmark_context": {
                      "type": "object",
                      "nullable": true,
                      "description": "The full serialized BenchmarkContext (methodology, rebalance_schedule, aliases, proxy/components, …)."
                    },
                    "n_constituents": {
                      "type": "integer"
                    },
                    "top_constituents": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "bw_sym_id": {
                            "type": "string"
                          },
                          "weight": {
                            "type": "number"
                          }
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "404": {
            "description": "Benchmark not found (unknown id/alias or missing surface).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/data/benchmark-fit": {
      "get": {
        "summary": "BenchmarkFit — fit a portfolio surface to a benchmark",
        "description": "`BenchmarkFit` (the comparison facet — CANONICAL_INTELLIGENCE_OBJECTS.md §9): fit a subject portfolio's weight vector against a benchmark surface at a common teo (the subject's latest teo ≤ `as_of`; the benchmark then at its latest teo ≤ the subject's teo — never peeking ahead). Returns active share, an active-weight RMS (a coarse tracking-error *proxy* — factor-based TE is a follow-on), overlap, and the top over/underweights. `subject` = a `BW-*` portfolio id (`BW-FUND-…`, `BW-FILER-…`, `BW-ETF-…`, `BW-BENCH-…`) or an ETF ticker (→ `BW-ETF-{TICKER}`); `benchmark` = a `bw_bench_id` or an alias (`SPY`, `70/30`, …) — the static benches, free on the gateway plane — or a custom bench (billed $0.005/call, capability `bench-active-custom`, parity with per-entity fund reads): `ff_own` (free-float-cap weight of the subject's OWN holdings — conviction vs what market cap alone implies; held symbols without a valid cap are dropped and counted in `benchmark_provenance`, never synthesized), `cell_<9-box slug>` (e.g. `cell_large-growth` — the style cell's MV weight surface at the latest teo ≤ the subject's teo), or `all` (fan-out: SPY + ff_own + the subject's declared style cell when resolvable, one call, response `{ fits: [...], omitted: [...] }`). Readiness gate: benchmarks under development (hollow trailing teos, shallow history, unverifiable stores) are blocked with `409` before billing; on `all` they are listed in `omitted[]` with reason `under_development`.\n",
        "operationId": "getBenchmarkFit",
        "tags": [
          "Funds Data Plane"
        ],
        "security": [
          {
            "BearerAuth": []
          },
          {}
        ],
        "parameters": [
          {
            "name": "subject",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string"
            },
            "example": "BW-FUND-S000004310"
          },
          {
            "name": "benchmark",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "Static: bw_bench_id or alias (SPY, 70/30, …) — free. Custom (billed $0.005): ff_own, cell_<9-box slug> (large-value … small-growth), all.",
            "example": "SPY"
          },
          {
            "name": "as_of",
            "in": "query",
            "schema": {
              "type": "string",
              "format": "date"
            }
          },
          {
            "name": "top",
            "in": "query",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 100,
              "default": 10
            }
          }
        ],
        "responses": {
          "200": {
            "description": "BenchmarkFit result.",
            "headers": {
              "X-Data-As-Of": {
                "schema": {
                  "type": "string",
                  "format": "date"
                },
                "description": "the subject surface teo the fit was computed at."
              },
              "X-Benchmark-As-Of": {
                "schema": {
                  "type": "string",
                  "format": "date"
                },
                "description": "the benchmark surface teo the fit was computed at."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "fit_schema_version": {
                      "type": "string"
                    },
                    "subject_id": {
                      "type": "string"
                    },
                    "subject_source_kind": {
                      "type": "string"
                    },
                    "benchmark_context_id": {
                      "type": "string"
                    },
                    "benchmark_name": {
                      "type": "string"
                    },
                    "benchmark_kind": {
                      "type": "string",
                      "enum": [
                        "static",
                        "ff_own",
                        "cell"
                      ],
                      "description": "static = bw_bench_id surface; ff_own = own-holdings cap benchmark; cell = 9-box style cell."
                    },
                    "benchmark_provenance": {
                      "type": "object",
                      "nullable": true,
                      "description": "Present for ff_own / cell benches.",
                      "properties": {
                        "cap_var": {
                          "type": "string",
                          "enum": [
                            "free_float_market_cap",
                            "market_cap"
                          ],
                          "description": "ff_own — cap variable used (free-float preferred",
                          "total fallback)": null
                        },
                        "cap_coverage": {
                          "type": "number",
                          "description": "ff_own — share of subject weight with a valid cap (rest dropped + counted)"
                        },
                        "caps_as_of": {
                          "type": "string",
                          "format": "date",
                          "description": "ff_own — teo of the cap cross-section"
                        },
                        "n_cap_dropped": {
                          "type": "integer",
                          "description": "ff_own — held symbols dropped for a missing/invalid cap"
                        },
                        "cell_slug": {
                          "type": "string",
                          "description": "cell — the 9-box slug"
                        },
                        "cell_teo": {
                          "type": "string",
                          "format": "date",
                          "description": "cell — the cell surface teo"
                        }
                      }
                    },
                    "subject_teo": {
                      "type": "string",
                      "format": "date"
                    },
                    "benchmark_teo": {
                      "type": "string",
                      "format": "date"
                    },
                    "n_subject_holdings": {
                      "type": "integer"
                    },
                    "n_benchmark_constituents": {
                      "type": "integer"
                    },
                    "n_overlap": {
                      "type": "integer"
                    },
                    "active_share": {
                      "type": "number"
                    },
                    "active_weight_rms": {
                      "type": "number",
                      "description": "coarse tracking-error proxy"
                    },
                    "weight_in_benchmark": {
                      "type": "number"
                    },
                    "benchmark_coverage": {
                      "type": "number"
                    },
                    "top_overweights": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "bw_sym_id": {
                            "type": "string"
                          },
                          "subject_weight": {
                            "type": "number"
                          },
                          "benchmark_weight": {
                            "type": "number"
                          },
                          "active_weight": {
                            "type": "number"
                          }
                        }
                      }
                    },
                    "top_underweights": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "bw_sym_id": {
                            "type": "string"
                          },
                          "subject_weight": {
                            "type": "number"
                          },
                          "benchmark_weight": {
                            "type": "number"
                          },
                          "active_weight": {
                            "type": "number"
                          }
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Missing/invalid query params (incl. invalid cell_<slug>).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "402": {
            "description": "Custom bench (ff_own / cell_* / all) with insufficient balance.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "404": {
            "description": "Unknown benchmark, or no surface for the subject/benchmark (or no cap/cell data for a custom bench).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "409": {
            "description": "Benchmark is under development (readiness gate, pre-billing).",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string",
                      "example": "benchmark 'BW-BENCH-SPY' is under development"
                    },
                    "status": {
                      "type": "string",
                      "enum": [
                        "development"
                      ]
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/data/benchmark/{id}/portfolio": {
      "get": {
        "summary": "Benchmark L1/L2/L3 return decomposition time series",
        "description": "A benchmark's portfolio return decomposition (Funds_DAG `surface_portfolios_zarr`) — same `(teo,)` schema as `/api/data/etf/{ticker}/portfolio` and `/api/funds/{bw_fund_id}/portfolio`. `id` accepts a `bw_bench_id` (`BW-BENCH-SPY`) or an alias (`SPY`, `70/30`). `weight_basis` (reported in the response) is v1 = `latest_holdings_constant` — the factor profile of the benchmark's current composition over ERM3 monthly's full history (the right view for a benchmark's risk profile). `variance_shares` carries the diversification-credited full-window market/sector/subsector/residual shares. Soft gateway auth (public read).\n",
        "operationId": "getBenchmarkPortfolioSeries",
        "tags": [
          "Funds Data Plane"
        ],
        "security": [
          {
            "BearerAuth": []
          },
          {}
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "example": "SPY"
          },
          {
            "name": "start_date",
            "in": "query",
            "schema": {
              "type": "string",
              "format": "date"
            }
          },
          {
            "name": "end_date",
            "in": "query",
            "schema": {
              "type": "string",
              "format": "date"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Benchmark portfolio decomposition time series.",
            "headers": {
              "X-Data-As-Of": {
                "schema": {
                  "type": "string",
                  "format": "date"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SurfacePortfolioSeries"
                }
              }
            }
          },
          "404": {
            "description": "Benchmark not found (unknown id/alias) or no portfolio decomposition available.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/funds/snapshot/{bw_fund_id}": {
      "get": {
        "summary": "Composed fund snapshot (JSON)",
        "description": "Composed JSON snapshot for a single mutual fund. Bundles registry + latest metrics + top-25 holdings + L1/L2/L3 hedge + 12-month portfolio time series + cohort context (rank N of `cohort_size` on every metric the rankings table covers).\n\nThe matching server-rendered PDF is `/api/funds/snapshot.pdf/{bw_fund_id}` (Stage D.2.b, ✅ shipped). The SDK can render this JSON locally via `riskmodels.snapshots.f1_fund_tearsheet` (Stage D.3, planned).\n",
        "operationId": "getFundSnapshotJson",
        "tags": [
          "Funds"
        ],
        "x-pricing": {
          "capability_id": "fund-snapshot-json",
          "tier": "baseline",
          "cost_usd": 0.05,
          "billing_code": "fund_snapshot_json_v2"
        },
        "parameters": [
          {
            "name": "bw_fund_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "example": "BW-FUND-S000004310"
          }
        ],
        "responses": {
          "200": {
            "description": "Composed fund snapshot.",
            "headers": {
              "X-Data-As-Of": {
                "schema": {
                  "type": "string",
                  "format": "date"
                }
              },
              "X-Data-Filing-Date": {
                "schema": {
                  "type": "string",
                  "format": "date"
                }
              },
              "X-Risk-Model-Version": {
                "schema": {
                  "type": "string"
                }
              },
              "X-API-Cost-USD": {
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/FundSnapshotResponse"
                }
              }
            }
          },
          "402": {
            "description": "Insufficient prepaid balance.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/InsufficientBalance"
                }
              }
            }
          },
          "404": {
            "description": "Fund not found, or no funds_latest row.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/RateLimitExceeded"
                }
              }
            }
          }
        }
      }
    },
    "/funds/snapshot.pdf/{bw_fund_id}": {
      "get": {
        "summary": "Composed fund snapshot (PDF)",
        "description": "Server-rendered F1 fund tearsheet PDF (Stage D.2.b). Same composition as `/api/funds/snapshot/{bw_fund_id}` (JSON), rendered via Playwright through `app/(print)/render-snapshot/funds/[bw_fund_id]/page.tsx`. Letter landscape, single page.\n**Caching:** Content-keyed on `(user, bw_fund_id, report_date)` for 24h. Cache hits return $0 with `X-Cache: HIT`.\n",
        "operationId": "getFundSnapshotPdf",
        "tags": [
          "Funds"
        ],
        "x-pricing": {
          "capability_id": "fund-snapshot-pdf",
          "tier": "premium",
          "cost_usd": 1.25,
          "billing_code": "fund_snapshot_pdf_v2"
        },
        "parameters": [
          {
            "name": "bw_fund_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "example": "BW-FUND-S000004310"
          }
        ],
        "responses": {
          "200": {
            "description": "PDF tearsheet (Letter landscape).",
            "headers": {
              "X-Data-As-Of": {
                "schema": {
                  "type": "string",
                  "format": "date"
                }
              },
              "X-Data-Filing-Date": {
                "schema": {
                  "type": "string",
                  "format": "date"
                }
              },
              "X-Cache": {
                "schema": {
                  "type": "string",
                  "enum": [
                    "HIT",
                    "MISS"
                  ]
                }
              },
              "X-API-Cost-USD": {
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/pdf": {
                "schema": {
                  "type": "string",
                  "format": "binary"
                }
              }
            }
          },
          "401": {
            "description": "Authentication required.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "402": {
            "description": "Insufficient prepaid balance.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/InsufficientBalance"
                }
              }
            }
          },
          "404": {
            "description": "Fund not found, or no funds_latest row.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "503": {
            "description": "PDF rendering disabled (`PLAYWRIGHT_PDF_ENABLED` is not `true` on the runtime).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/funds/style/{slug}/snapshot": {
      "get": {
        "summary": "Composed cohort snapshot (JSON)",
        "description": "Composed JSON snapshot for a 9-box style cell — the differentiated wedge vs Morningstar. Bundles cohort metrics (EW + MV) + top-25 cohort holdings (MV) + 12-month cohort portfolio history (both weightings) + top-10 funds in cell (by 12-month gross return) + top-15 symbols in cell (by current weight, MV).\n\nMatching server-rendered PDF is `/api/funds/style/{slug}/snapshot.pdf` (Stage D.2.d, ✅ shipped).\n",
        "operationId": "getStyleCohortSnapshotJson",
        "tags": [
          "Funds"
        ],
        "x-pricing": {
          "capability_id": "style-cohort-snapshot-json",
          "tier": "baseline",
          "cost_usd": 0.02,
          "billing_code": "style_cohort_snapshot_json_v2"
        },
        "parameters": [
          {
            "name": "slug",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "enum": [
                "large-value",
                "large-blend",
                "large-growth",
                "mid-value",
                "mid-blend",
                "mid-growth",
                "small-value",
                "small-blend",
                "small-growth"
              ]
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Composed cohort snapshot.",
            "headers": {
              "X-Data-As-Of": {
                "schema": {
                  "type": "string",
                  "format": "date"
                }
              },
              "X-Data-Filing-Date": {
                "schema": {
                  "type": "string",
                  "format": "date"
                }
              },
              "X-Risk-Model-Version": {
                "schema": {
                  "type": "string"
                }
              },
              "X-API-Cost-USD": {
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CohortSnapshotResponse"
                }
              }
            }
          },
          "400": {
            "description": "Invalid style slug.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "402": {
            "description": "Insufficient prepaid balance.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/InsufficientBalance"
                }
              }
            }
          },
          "404": {
            "description": "No cohort metrics available for this cell.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/RateLimitExceeded"
                }
              }
            }
          }
        }
      }
    },
    "/funds/style/{slug}/snapshot.pdf": {
      "get": {
        "summary": "Composed cohort snapshot (PDF)",
        "description": "Server-rendered C1 cohort tearsheet PDF (Stage D.2.d). Same composition as `/api/funds/style/{slug}/snapshot` (JSON), rendered via Playwright through `app/(print)/render-snapshot/funds/style/[slug]/page.tsx`. Letter landscape, single page. Differentiated wedge: cohort aggregates EW + MV, top funds, top symbols, cohort top holdings — content Morningstar does not present at this granularity.\n**Caching:** Content-keyed on `(user, slug, report_date)` for 24h.\n",
        "operationId": "getStyleCohortSnapshotPdf",
        "tags": [
          "Funds"
        ],
        "x-pricing": {
          "capability_id": "style-cohort-snapshot-pdf",
          "tier": "premium",
          "cost_usd": 0.5,
          "billing_code": "style_cohort_snapshot_pdf_v2"
        },
        "parameters": [
          {
            "name": "slug",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "enum": [
                "large-value",
                "large-blend",
                "large-growth",
                "mid-value",
                "mid-blend",
                "mid-growth",
                "small-value",
                "small-blend",
                "small-growth"
              ]
            }
          }
        ],
        "responses": {
          "200": {
            "description": "PDF cohort tearsheet (Letter landscape).",
            "headers": {
              "X-Data-As-Of": {
                "schema": {
                  "type": "string",
                  "format": "date"
                }
              },
              "X-Data-Filing-Date": {
                "schema": {
                  "type": "string",
                  "format": "date"
                }
              },
              "X-Cache": {
                "schema": {
                  "type": "string",
                  "enum": [
                    "HIT",
                    "MISS"
                  ]
                }
              },
              "X-API-Cost-USD": {
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/pdf": {
                "schema": {
                  "type": "string",
                  "format": "binary"
                }
              }
            }
          },
          "400": {
            "description": "Invalid style slug.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "401": {
            "description": "Authentication required.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "402": {
            "description": "Insufficient prepaid balance.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/InsufficientBalance"
                }
              }
            }
          },
          "404": {
            "description": "No cohort metrics available for this cell.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "503": {
            "description": "PDF rendering disabled (`PLAYWRIGHT_PDF_ENABLED` is not `true` on the runtime).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/funds/style/{slug}/portfolio": {
      "get": {
        "summary": "Per-cell cohort portfolio time series",
        "description": "Time series of cohort portfolio metrics from Slice 6's per-cell `ds_portfolio.zarr` on GCS. Each row carries both EW and MV blocks side-by-side. Optional inclusive `start_date` / `end_date` params trim the panel.\n",
        "operationId": "getStyleCohortPortfolioHistory",
        "tags": [
          "Funds"
        ],
        "x-pricing": {
          "capability_id": "style-cohort-portfolio-history",
          "tier": "baseline",
          "cost_usd": 0.02,
          "billing_code": "style_cohort_portfolio_history_v2"
        },
        "parameters": [
          {
            "name": "slug",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "start_date",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "format": "date"
            }
          },
          {
            "name": "end_date",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "format": "date"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Cohort time series rows.",
            "headers": {
              "X-Data-As-Of": {
                "schema": {
                  "type": "string",
                  "format": "date"
                }
              },
              "X-API-Cost-USD": {
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CohortPortfolioHistoryResponse"
                }
              }
            }
          },
          "400": {
            "description": "Invalid slug or malformed date params.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "402": {
            "description": "Insufficient prepaid balance.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/InsufficientBalance"
                }
              }
            }
          },
          "404": {
            "description": "No cohort portfolio history available for this cell.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/RateLimitExceeded"
                }
              }
            }
          }
        }
      }
    },
    "/funds/style/{slug}/holdings": {
      "get": {
        "summary": "Top-N cohort holdings within a 9-box style cell",
        "description": "Top-N cohort holdings at the latest teo. Reads `weight (teo, symbol, weighting)` and `contribution_*` / `n_funds_holding` from Slice 5b's per-cell `ds_symbols.zarr`. Sorted by `weight` descending.\n\n`?weighting` defaults to `mv` (market-cap-weighted, Morningstar- comparable). Switch to `ew` to see equal-weight cohort exposures — radically different top-N lists for non-uniform cells.\n",
        "operationId": "getStyleCohortHoldings",
        "tags": [
          "Funds"
        ],
        "x-pricing": {
          "capability_id": "style-cohort-holdings",
          "tier": "baseline",
          "cost_usd": 0.02,
          "billing_code": "style_cohort_holdings_v2"
        },
        "parameters": [
          {
            "name": "slug",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "weighting",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "ew",
                "mv"
              ],
              "default": "mv"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 100,
              "default": 25
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Top-N cohort holdings snapshot.",
            "headers": {
              "X-Data-As-Of": {
                "schema": {
                  "type": "string",
                  "format": "date"
                }
              },
              "X-API-Cost-USD": {
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CohortHoldingsResponse"
                }
              }
            }
          },
          "400": {
            "description": "Invalid slug, weighting, or limit.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "402": {
            "description": "Insufficient prepaid balance.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/InsufficientBalance"
                }
              }
            }
          },
          "404": {
            "description": "No cohort holdings panel available.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/RateLimitExceeded"
                }
              }
            }
          }
        }
      }
    },
    "/funds/style/{slug}": {
      "get": {
        "summary": "Latest cohort metrics for a 9-box style cell",
        "description": "Latest portfolio return decomposition + diagnostics for a 9-box style cell, aggregated across all funds in the cell. Both EW and MV cohort portfolios are returned side-by-side under `weightings`.\n\nThe differentiated wedge: Morningstar reports per-fund metrics but doesn't expose cohort aggregates with this attribution depth.\n",
        "operationId": "getStyleCohortMetrics",
        "tags": [
          "Funds"
        ],
        "x-pricing": {
          "capability_id": "style-cohort-metrics",
          "tier": "baseline",
          "cost_usd": 0.02,
          "billing_code": "style_cohort_metrics_v2"
        },
        "parameters": [
          {
            "name": "slug",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "enum": [
                "large-value",
                "large-blend",
                "large-growth",
                "mid-value",
                "mid-blend",
                "mid-growth",
                "small-value",
                "small-blend",
                "small-growth"
              ]
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Latest cohort metrics (both weightings).",
            "headers": {
              "X-Data-As-Of": {
                "schema": {
                  "type": "string",
                  "format": "date"
                }
              },
              "X-Data-Filing-Date": {
                "schema": {
                  "type": "string",
                  "format": "date"
                }
              },
              "X-Risk-Model-Version": {
                "schema": {
                  "type": "string"
                }
              },
              "X-API-Cost-USD": {
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/StyleCohortMetricsResponse"
                }
              }
            }
          },
          "400": {
            "description": "Invalid style slug.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "402": {
            "description": "Insufficient prepaid balance.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/InsufficientBalance"
                }
              }
            }
          },
          "404": {
            "description": "No cohort metrics available for this cell.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/RateLimitExceeded"
                }
              }
            }
          }
        }
      }
    },
    "/funds/style/{slug}/rankings/{cohort_type}": {
      "get": {
        "summary": "Top-N rankings within a 9-box style cell",
        "description": "Top-N rankings within a 9-box style cell × cohort_type × metric × period_window × weighting. `cohort_type` selects what gets ranked: symbols (holdings), sector codes, or funds.\n\nFor `cohort_type=fund`, the `weighting` parameter is ignored (writer stores `'ew'` placeholder since fund returns are scalar). Top-N is capped at 50 — the data ceiling per Slice 9.\n",
        "operationId": "getStyleCohortRankings",
        "tags": [
          "Funds"
        ],
        "x-pricing": {
          "capability_id": "style-cohort-rankings",
          "tier": "baseline",
          "cost_usd": 0.02,
          "billing_code": "style_cohort_rankings_v2"
        },
        "parameters": [
          {
            "name": "slug",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "cohort_type",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "enum": [
                "symbol",
                "sector",
                "fund"
              ]
            }
          },
          {
            "name": "metric",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "Metric to rank by (e.g. weight, gross_return, n_funds_holding)."
          },
          {
            "name": "period_window",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "1m",
                "3m",
                "12m",
                "36m"
              ],
              "default": "1m"
            }
          },
          {
            "name": "weighting",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "ew",
                "mv"
              ],
              "default": "mv"
            },
            "description": "Ignored for cohort_type=fund."
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 50,
              "default": 25
            }
          },
          {
            "name": "include_inactive",
            "in": "query",
            "required": false,
            "schema": {
              "type": "boolean",
              "default": false
            },
            "description": "cohort_type=fund only. When true, keeps funds that are not active for listing. Stored ranks are returned unchanged, so filtered output can have rank gaps.\n"
          },
          {
            "name": "include_etfs",
            "in": "query",
            "required": false,
            "schema": {
              "type": "boolean",
              "default": true
            },
            "description": "cohort_type=fund only. When false, drops funds flagged `is_etf`."
          }
        ],
        "responses": {
          "200": {
            "description": "Top-N ranked rows.",
            "headers": {
              "X-Data-As-Of": {
                "schema": {
                  "type": "string",
                  "format": "date"
                }
              },
              "X-Data-Filing-Date": {
                "schema": {
                  "type": "string",
                  "format": "date"
                }
              },
              "X-API-Cost-USD": {
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/StyleCohortRankingsResponse"
                }
              }
            }
          },
          "400": {
            "description": "Invalid slug, cohort_type, period_window, weighting, or missing metric.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "402": {
            "description": "Insufficient prepaid balance.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/InsufficientBalance"
                }
              }
            }
          },
          "404": {
            "description": "No rankings for this combination.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/RateLimitExceeded"
                }
              }
            }
          }
        }
      }
    },
    "/funds/{bw_fund_id}/hedge": {
      "get": {
        "summary": "Latest fund hedge ratios (L1 / L2 / L3)",
        "description": "Latest L1/L2/L3 ETF hedge ratios for a fund. Reads `L{1,2,3}_HR (teo, symbol)` from Slice 7's per-fund `ds_hr.zarr` at the latest teo. Empty arrays are emitted for levels with no non-NaN entries (small / niche funds may only have an L1 market hedge).\n",
        "operationId": "getFundHedge",
        "tags": [
          "Funds"
        ],
        "x-pricing": {
          "capability_id": "fund-hedge",
          "tier": "baseline",
          "cost_usd": 0.02,
          "billing_code": "fund_hedge_v2"
        },
        "parameters": [
          {
            "name": "bw_fund_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "example": "BW-FUND-S000004310"
          }
        ],
        "responses": {
          "200": {
            "description": "Per-level hedge legs.",
            "headers": {
              "X-Data-As-Of": {
                "schema": {
                  "type": "string",
                  "format": "date"
                }
              },
              "X-Data-Filing-Date": {
                "schema": {
                  "type": "string",
                  "format": "date"
                }
              },
              "X-API-Cost-USD": {
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/FundHedgeResponse"
                }
              }
            }
          },
          "402": {
            "description": "Insufficient prepaid balance.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/InsufficientBalance"
                }
              }
            }
          },
          "404": {
            "description": "Fund not found, or no hedge ratio panel.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/RateLimitExceeded"
                }
              }
            }
          }
        }
      }
    },
    "/funds/{bw_fund_id}/holdings": {
      "get": {
        "summary": "Top-N current fund holdings",
        "description": "Top-N holdings for a fund at the latest teo. Reads `adj_mv (symbol, teo)` and `aum_erm3 (teo,)` from Slice 5's per-fund `ds_ph.zarr` on GCS, sorts by `adj_mv` descending, and returns `bw_sym_id` + `adj_mv` + `weight = adj_mv / aum_erm3`. Resolve `bw_sym_id` to ticker via `/api/data/symbols/batch` if needed.\n",
        "operationId": "getFundHoldings",
        "tags": [
          "Funds"
        ],
        "x-pricing": {
          "capability_id": "fund-holdings",
          "tier": "baseline",
          "cost_usd": 0.02,
          "billing_code": "fund_holdings_v2"
        },
        "parameters": [
          {
            "name": "bw_fund_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "example": "BW-FUND-S000004310"
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 1000,
              "default": 25
            },
            "description": "Max holdings to return (default 25)."
          }
        ],
        "responses": {
          "200": {
            "description": "Top-N holdings snapshot.",
            "headers": {
              "X-Data-As-Of": {
                "schema": {
                  "type": "string",
                  "format": "date"
                },
                "description": "Latest teo in the per-fund holdings panel."
              },
              "X-Data-Filing-Date": {
                "schema": {
                  "type": "string",
                  "format": "date"
                }
              },
              "X-API-Cost-USD": {
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/FundHoldingsResponse"
                }
              }
            }
          },
          "400": {
            "description": "Malformed limit param.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "402": {
            "description": "Insufficient prepaid balance.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/InsufficientBalance"
                }
              }
            }
          },
          "404": {
            "description": "Fund not found, or no holdings panel.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/RateLimitExceeded"
                }
              }
            }
          }
        }
      }
    },
    "/funds/{bw_fund_id}/portfolio": {
      "get": {
        "summary": "Per-fund portfolio time series",
        "description": "Time series of portfolio-level return components and diagnostics from Slice 8's per-fund `ds_portfolio.zarr` on GCS. One row per teo (month-end). Optional inclusive `start_date` / `end_date` params trim the panel.\n\nBitemporal: `X-Data-As-Of` reflects the latest teo in the returned slice; `X-Data-Filing-Date` carries the registry's `latest_filing_date`.\n",
        "operationId": "getFundPortfolioHistory",
        "tags": [
          "Funds"
        ],
        "x-pricing": {
          "capability_id": "fund-portfolio-history",
          "tier": "baseline",
          "cost_usd": 0.02,
          "billing_code": "fund_portfolio_history_v2"
        },
        "parameters": [
          {
            "name": "bw_fund_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "example": "BW-FUND-S000004310"
          },
          {
            "name": "start_date",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "format": "date"
            },
            "description": "Inclusive lower bound (YYYY-MM-DD). Default = first teo in the fund's panel."
          },
          {
            "name": "end_date",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "format": "date"
            },
            "description": "Inclusive upper bound (YYYY-MM-DD). Default = latest teo in the fund's panel."
          }
        ],
        "responses": {
          "200": {
            "description": "Time series rows.",
            "headers": {
              "X-Data-As-Of": {
                "schema": {
                  "type": "string",
                  "format": "date"
                },
                "description": "Latest teo in the returned slice."
              },
              "X-Data-Filing-Date": {
                "schema": {
                  "type": "string",
                  "format": "date"
                },
                "description": "funds.latest_filing_date for the fund."
              },
              "X-API-Cost-USD": {
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/FundPortfolioHistoryResponse"
                }
              }
            }
          },
          "400": {
            "description": "Malformed date params or start_date > end_date.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "402": {
            "description": "Insufficient prepaid balance.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/InsufficientBalance"
                }
              }
            }
          },
          "404": {
            "description": "Fund not found, or zarr returned no rows for the date window.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/RateLimitExceeded"
                }
              }
            }
          }
        }
      }
    },
    "/funds/{bw_fund_id}/nav": {
      "get": {
        "summary": "Per-fund NAV time series (yfinance)",
        "description": "Time series of actual fund NAV from yfinance, resampled to month-end and stored in the per-fund `ds_nav.zarr` on GCS (Funds_DAG `fund_nav_zarr` asset). One row per teo with `nav_close` (month-end close) and `nav_return_monthly` (pct_change of consecutive closes).\n\nPairs with `/funds/{bw_fund_id}/portfolio`: portfolio returns are 13F-derived holdings attribution; NAV returns are what investors actually realised. The gap surfaces intra-quarter trading, fees, and cash drag not visible in 13F.\n\nReturns 404 when the fund has no yfinance-resolvable ticker (institutional SMAs, separately-managed accounts).\n\nBitemporal: `X-Data-As-Of` reflects the latest teo in the returned slice; `X-Data-Filing-Date` carries the registry's `latest_filing_date`.\n",
        "operationId": "getFundNavHistory",
        "tags": [
          "Funds"
        ],
        "x-pricing": {
          "capability_id": "fund-nav-history",
          "tier": "baseline",
          "cost_usd": 0.02,
          "billing_code": "fund_nav_history_v2"
        },
        "parameters": [
          {
            "name": "bw_fund_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "example": "BW-FUND-S000001243"
          },
          {
            "name": "start_date",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "format": "date"
            },
            "description": "Inclusive lower bound (YYYY-MM-DD). Default = first teo in the fund's NAV panel."
          },
          {
            "name": "end_date",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "format": "date"
            },
            "description": "Inclusive upper bound (YYYY-MM-DD). Default = latest teo in the fund's NAV panel."
          }
        ],
        "responses": {
          "200": {
            "description": "NAV time series rows.",
            "headers": {
              "X-Data-As-Of": {
                "schema": {
                  "type": "string",
                  "format": "date"
                },
                "description": "Latest teo in the returned slice."
              },
              "X-Data-Filing-Date": {
                "schema": {
                  "type": "string",
                  "format": "date"
                },
                "description": "funds.latest_filing_date for the fund."
              },
              "X-API-Cost-USD": {
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/FundNavHistoryResponse"
                }
              }
            }
          },
          "400": {
            "description": "Malformed date params or start_date > end_date.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "402": {
            "description": "Insufficient prepaid balance.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/InsufficientBalance"
                }
              }
            }
          },
          "404": {
            "description": "Fund not found, or no NAV zarr for the date window (e.g. institutional SMA without a yfinance ticker).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/RateLimitExceeded"
                }
              }
            }
          }
        }
      }
    },
    "/funds/{bw_fund_id}": {
      "get": {
        "summary": "Latest fund metrics",
        "description": "Knowledge-mode portfolio return decomposition + diagnostics for a single mutual fund. Returns the gross / market / sector / subsector / idiosyncratic return components from the per-fund Slice 8 zarr (materialized in `public.funds_latest`), plus diagnostics (`weight_sum`, `n_holdings_active`, `effective_n`, `top10_weight_sum`) and registry-side meta. Bitemporal lineage on response headers (`X-Data-As-Of`, `X-Data-Filing-Date`).\n\nPer-fund time series, holdings panel, and hedge ratios live in GCS Zarr and ship under `/funds/{bw_fund_id}/portfolio`, `/holdings`, `/hedge` in Stage B.2.\n\nv1 returns the latest knowledge-mode answer only — `?as_of=` / `?mode=` is deferred to v2 (schema is forward-compatible).\n",
        "operationId": "getFundMetrics",
        "tags": [
          "Funds"
        ],
        "x-pricing": {
          "capability_id": "fund-metrics",
          "tier": "baseline",
          "cost_usd": 0.02,
          "billing_code": "fund_metrics_v2"
        },
        "parameters": [
          {
            "name": "bw_fund_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "example": "BW-FUND-S000004310"
          }
        ],
        "responses": {
          "200": {
            "description": "Latest fund metrics.",
            "headers": {
              "X-Data-As-Of": {
                "schema": {
                  "type": "string",
                  "format": "date"
                },
                "description": "funds_latest.report_date (period end of the snapshot)."
              },
              "X-Data-Filing-Date": {
                "schema": {
                  "type": "string",
                  "format": "date"
                },
                "description": "funds_latest.filing_date (SEC acceptance date)."
              },
              "X-Risk-Model-Version": {
                "schema": {
                  "type": "string"
                }
              },
              "X-API-Cost-USD": {
                "schema": {
                  "type": "string"
                },
                "description": "USD cost deducted from prepaid balance for this request."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/FundMetricsResponse"
                }
              }
            }
          },
          "402": {
            "description": "Insufficient prepaid balance.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/InsufficientBalance"
                }
              }
            }
          },
          "404": {
            "description": "Fund not found, or registry exists but no funds_latest row yet.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/RateLimitExceeded"
                }
              }
            }
          }
        }
      }
    },
    "/13f/filers/search": {
      "get": {
        "summary": "13F filer search",
        "description": "Discovery surface for 13F filers. Search by name, CIK, or LEI; filter by filer_type / aum_tier; optionally restrict to filers passing the modelability gate. Per-filer follow-up calls under `/13f/filers/{bw_filer_id}/*` are metered.\n\n**Authentication:** None — this endpoint is public. Like `/funds/search`, it is wrapped in `withBilling` with `skipBilling: true`, which bypasses key validation entirely.\n**Billing:** Free\n**Rate Limit:** 60 requests/minute per IP (`FILER_SEARCH_IP_RPM`). `limit` is capped at 100 rows.\n",
        "operationId": "searchFilers",
        "tags": [
          "13F Filers"
        ],
        "security": [],
        "x-pricing": {
          "capability_id": "filer-search",
          "tier": "baseline",
          "cost_usd": 0,
          "billing_code": "filer_search_v1"
        },
        "parameters": [
          {
            "name": "q",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "filer_type",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "aum_tier",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "modelable_only",
            "in": "query",
            "required": false,
            "schema": {
              "type": "boolean"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "default": 50,
              "maximum": 100
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Filer search results.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "results": {
                      "type": "array",
                      "items": {
                        "type": "object"
                      }
                    }
                  }
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded."
          }
        }
      }
    },
    "/13f/filers/{bw_filer_id}": {
      "get": {
        "summary": "Latest 13F filer metrics",
        "description": "Latest knowledge-mode portfolio metrics for a single 13F filer. Reads `public.filers` + `public.filer_portfolios_latest`. Returns diagnostics, AUM (total + in-ERM3), ERM3-coverage modelability inputs, and portfolio-derived 9-box style attribution. Return components are NULL until D.8 Phase 2. NAV is permanently absent — filers have no NAV time series.\n",
        "operationId": "getFilerMetrics",
        "tags": [
          "13F Filers"
        ],
        "x-pricing": {
          "capability_id": "filer-metrics",
          "tier": "baseline",
          "cost_usd": 0.02,
          "billing_code": "filer_metrics_v2"
        },
        "parameters": [
          {
            "name": "bw_filer_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "example": "BW-FILER-CIK0001067983"
          }
        ],
        "responses": {
          "200": {
            "description": "Latest filer metrics.",
            "headers": {
              "X-Data-As-Of": {
                "schema": {
                  "type": "string",
                  "format": "date"
                }
              },
              "X-Data-Filing-Date": {
                "schema": {
                  "type": "string",
                  "format": "date"
                }
              },
              "X-Risk-Model-Version": {
                "schema": {
                  "type": "string"
                }
              },
              "X-API-Cost-USD": {
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          },
          "402": {
            "description": "Insufficient prepaid balance.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/InsufficientBalance"
                }
              }
            }
          },
          "404": {
            "description": "Filer not found.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded."
          }
        }
      }
    },
    "/13f/filers/{bw_filer_id}/holdings": {
      "get": {
        "summary": "13F filer top holdings",
        "description": "Top-N holdings at the filer's latest teo — or, with `as_of`, at the latest quarter *known* by that date (knowledge mode: selection on `filing_date <= as_of`; falls back to `report_date <= as_of` on panels without per-quarter filing dates, with the basis echoed as `as_of_basis`). Reads per-filer ds_ph.zarr from GCS. Each holding carries `security_id` (post-D.8.1 = bw_sym_id; pre-migration = a raw 9-char security identifier), `adj_mv`, and `weight` (fraction of total in-portfolio AUM). Bi-temporal stamps are body fields — `report_date` (valid time, = teo) and `filing_date` (knowledge time, EDGAR date_filed of the surviving submission; null until the per-quarter data fill lands) — mirrored on the X-Data-As-Of / X-Data-Filing-Date headers.\n\nThe surviving submission's filing identity is reported alongside them, at the response root (one accession state per selected quarter, not per holding): `accession_number`, `filing_type` (the SEC **form type** — `13F-HR`, `13F-HR/A`, `13F-NT` — not the report quarter) and `amendment_type` (cover-page semantics: `ORIGINAL`, `RESTATEMENT`, `NEW_HOLDINGS`, `UNKNOWN`). Use them to tell an original filing from an amendment, and a restatement of previously reported positions from an amendment that only adds new ones. All three are `null` on panels published before the accession-vintage stores; `amendment_type` is never inferred from a `/A` suffix, so a null means \"not classified upstream\", which is distinct from the `UNKNOWN` value.\n",
        "operationId": "getFilerHoldings",
        "tags": [
          "13F Filers"
        ],
        "x-pricing": {
          "capability_id": "filer-holdings",
          "tier": "baseline",
          "cost_usd": 0.02,
          "billing_code": "filer_holdings_v2"
        },
        "parameters": [
          {
            "name": "bw_filer_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "Canonical filer id (BW-FILER-CIK{cik}). Composite entities (BW-SYNTH-*) are served by the same route; registry-only fields (cik, filer_type, aum_tier) return null.\n"
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "default": 25,
              "maximum": 1000
            }
          },
          {
            "name": "as_of",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "format": "date"
            },
            "description": "Knowledge-mode date: serve the latest quarter whose filing was public on or before this date. 404 when nothing was known yet.\n"
          }
        ],
        "responses": {
          "200": {
            "description": "Top-N holdings at the selected teo, with `report_date`, `filing_date`, the selected accession's filing identity (`accession_number`, `filing_type`, `amendment_type`), and (under `as_of`) `as_of_basis`.\n",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "additionalProperties": true,
                  "properties": {
                    "report_date": {
                      "type": "string",
                      "format": "date",
                      "description": "Valid time — the 13F reporting period end (= teo) the holdings describe.\n"
                    },
                    "filing_date": {
                      "type": "string",
                      "format": "date",
                      "nullable": true,
                      "description": "Knowledge time — EDGAR date_filed of the surviving submission for this quarter. Null on panels without per-quarter filing dates.\n"
                    },
                    "as_of_basis": {
                      "type": "string",
                      "enum": [
                        "filing_date",
                        "report_date"
                      ],
                      "description": "Which axis resolved an `as_of` selection.\n"
                    },
                    "accession_number": {
                      "type": "string",
                      "nullable": true,
                      "description": "EDGAR accession number of the surviving submission for the selected quarter — the filing whose positions this panel reports. Null on panels published before the accession-vintage stores.\n",
                      "example": "0000950123-24-008496"
                    },
                    "filing_type": {
                      "type": "string",
                      "nullable": true,
                      "description": "SEC **submission/form type** of that accession — not the report quarter. `13F-HR` is an original holdings report, `13F-HR/A` an amendment to one, `13F-NT` a notice filing. Null when the form type is not published for this panel.\n",
                      "example": "13F-HR/A"
                    },
                    "amendment_type": {
                      "type": "string",
                      "nullable": true,
                      "enum": [
                        "ORIGINAL",
                        "RESTATEMENT",
                        "NEW_HOLDINGS",
                        "UNKNOWN"
                      ],
                      "description": "Cover-page amendment semantics of that accession, as classified upstream. `ORIGINAL` — not an amendment. `RESTATEMENT` — the amendment replaces the previously reported holdings in full. `NEW_HOLDINGS` — it adds holdings on top of the prior filing. `UNKNOWN` — an amendment whose cover page did not declare which. Null means no upstream classification is published for this panel, which is *not* the same as `UNKNOWN`; it is never inferred from a `/A` suffix.\n",
                      "example": "RESTATEMENT"
                    },
                    "holdings": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "additionalProperties": true
                      }
                    }
                  }
                }
              }
            }
          },
          "402": {
            "description": "Insufficient prepaid balance.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/InsufficientBalance"
                }
              }
            }
          },
          "404": {
            "description": "Filer not found, or no holdings panel available.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded."
          }
        }
      }
    },
    "/13f/filers/{bw_filer_id}/portfolio": {
      "get": {
        "summary": "13F filer portfolio history",
        "description": "Per-filer portfolio time series from per-filer ds_portfolio.zarr. One row per teo (quarter-end) with diagnostics, AUM, ERM3 coverage, and portfolio style attribution. Return components are NULL until D.8 Phase 2 (the security-master ↔ ERM3 attribution bridge). Each row carries its `filing_date` (knowledge-time stamp; null until the per-quarter data fill lands). `as_of` filters to rows known by that date (`filing_date <= as_of`, falling back to `teo <= as_of` on panels without filing dates; basis echoed as `as_of_basis`).\n",
        "operationId": "getFilerPortfolioHistory",
        "tags": [
          "13F Filers"
        ],
        "x-pricing": {
          "capability_id": "filer-portfolio-history",
          "tier": "baseline",
          "cost_usd": 0.02,
          "billing_code": "filer_portfolio_history_v2"
        },
        "parameters": [
          {
            "name": "bw_filer_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "start_date",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "format": "date"
            }
          },
          {
            "name": "end_date",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "format": "date"
            }
          },
          {
            "name": "as_of",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "format": "date"
            },
            "description": "Knowledge-mode date: keep only quarters whose filings were public on or before this date. 404 when nothing was known yet.\n"
          }
        ],
        "responses": {
          "200": {
            "description": "Portfolio history rows (each with `filing_date`).",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          },
          "402": {
            "description": "Insufficient prepaid balance.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/InsufficientBalance"
                }
              }
            }
          },
          "404": {
            "description": "Filer not found, or no portfolio history available.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded."
          }
        }
      }
    },
    "/13f/filers/{bw_filer_id}/concentration": {
      "get": {
        "summary": "13F filer concentration summary",
        "description": "Quarter-end concentration panel from per-filer ds_portfolio.zarr. Returns median and latest effective N, top-5 / top-10 weight share, and weight HHI over an optional date window (same underlying series as /portfolio, summarized for diligence).\n",
        "operationId": "getFilerConcentration",
        "tags": [
          "13F Filers"
        ],
        "x-pricing": {
          "capability_id": "filer-concentration",
          "tier": "baseline",
          "cost_usd": 0.02,
          "billing_code": "filer_concentration_v2"
        },
        "parameters": [
          {
            "name": "bw_filer_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "start_date",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "format": "date"
            }
          },
          {
            "name": "end_date",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "format": "date"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Concentration summary (median + latest).",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          },
          "402": {
            "description": "Insufficient prepaid balance.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/InsufficientBalance"
                }
              }
            }
          },
          "404": {
            "description": "Filer not found, or no concentration panel available.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded."
          }
        }
      }
    },
    "/13f/filers/{bw_filer_id}/snapshot": {
      "get": {
        "summary": "13F filer composed snapshot (JSON)",
        "description": "Single-call composed snapshot for a 13F filer: registry + latest metrics + top-25 holdings + 12mo portfolio history + cohort ranks (filer_type and aum_tier partitions) + portfolio-derived 9-box style attribution + ERM3 coverage diagnostics + modelability flag. NAV is intentionally absent (`_metadata.nav_applicable: false`) — filers have no NAV time series. Adds `erm3_decomposition` from filer `ds_returns_monthly.zarr` (monthly L3 rows, variance share attrs, latest-month waterfall) when present (D.8.22). Holdings may include latest daily L3 ER shares per `bw_sym_id` from `security_history_latest`. `hedge_sleeve` is populated when filer `ds_hr.zarr` exists (D.8.10 Phase 3); otherwise null.\n",
        "operationId": "getFilerSnapshot",
        "tags": [
          "13F Filers"
        ],
        "x-pricing": {
          "capability_id": "filer-snapshot-json",
          "tier": "premium",
          "cost_usd": 0.05,
          "billing_code": "filer_snapshot_json_v2"
        },
        "parameters": [
          {
            "name": "bw_filer_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "Canonical filer id (BW-FILER-CIK{cik}). Composite entities (BW-SYNTH-*) are served by the same route; the snapshot adds entity_kind: synthetic_composite, evidence_class: reconstructed, recipe, and composition coverage; registry-only fields return null.\n"
          }
        ],
        "responses": {
          "200": {
            "description": "Composed FilerSnapshot.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          },
          "402": {
            "description": "Insufficient prepaid balance.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/InsufficientBalance"
                }
              }
            }
          },
          "404": {
            "description": "Filer not found.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded."
          }
        }
      }
    },
    "/13f/filers/{bw_filer_id}/snapshot.pdf": {
      "get": {
        "summary": "13F filer F1 tearsheet (PDF)",
        "description": "Rendered 1-page F1 tearsheet PDF for a 13F filer. Same content as /snapshot (JSON). NAV section is absent by design. Server-rendered via headless Chromium (Playwright); requires PLAYWRIGHT_PDF_ENABLED=true on the API runtime.\n",
        "operationId": "getFilerSnapshotPdf",
        "tags": [
          "13F Filers"
        ],
        "x-pricing": {
          "capability_id": "filer-snapshot-pdf",
          "tier": "premium",
          "cost_usd": 0.25,
          "billing_code": "filer_snapshot_pdf_v2"
        },
        "parameters": [
          {
            "name": "bw_filer_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "PDF tearsheet bytes.",
            "content": {
              "application/pdf": {
                "schema": {
                  "type": "string",
                  "format": "binary"
                }
              }
            }
          },
          "402": {
            "description": "Insufficient prepaid balance."
          },
          "404": {
            "description": "Filer not found."
          },
          "503": {
            "description": "PDF rendering disabled (PLAYWRIGHT_PDF_ENABLED unset)."
          }
        }
      }
    }
  },
  "tags": [
    {
      "name": "Risk Metrics",
      "description": "ERM3 factor hedge ratios, explained risk, and return decompositions."
    },
    {
      "name": "Utility",
      "description": "Ticker search, health, and service discovery."
    },
    {
      "name": "Account",
      "description": "Balance, billing, and invoice management."
    },
    {
      "name": "Webhooks",
      "description": "Outbound webhook subscriptions (e.g. batch completion)."
    },
    {
      "name": "Authentication",
      "description": "API key provisioning and OAuth2 token management."
    },
    {
      "name": "MCP",
      "description": "Model Context Protocol server endpoints for AI agents."
    },
    {
      "name": "Plaid Integration",
      "description": "Live portfolio synchronization via Plaid Investments API."
    },
    {
      "name": "Compliance",
      "description": "AI marketplace compliance and discovery manifests."
    },
    {
      "name": "Privacy",
      "description": "Data handling and privacy disclosure."
    },
    {
      "name": "Discovery",
      "description": "Service discovery and capability manifests."
    },
    {
      "name": "Funds Data Plane",
      "description": "Raw-shape reads against the funds Supabase tables (`funds`, `funds_latest`). Latest knowledge-mode snapshot only — history reads via the per-fund Zarr endpoints in later stages. Public; soft Bearer auth; not metered. The compute-bearing `/funds/*`, `/funds/style/*`, and `/funds/snapshot/*` surfaces ship in subsequent stages.\n"
    },
    {
      "name": "Funds",
      "description": "Compute-bearing fund endpoints. Latest metrics (Stage B.1) join `public.funds` with `public.funds_latest` and return a shaped response with bitemporal lineage. Per-fund time series, holdings, and hedge ratios (Stage B.2) read from GCS Zarr. Style-cohort surfaces (Stage C) and snapshot tearsheets (Stage D) follow. Billable per request.\n"
    },
    {
      "name": "13F Filers",
      "description": "Compute-bearing 13F filer endpoints (D.8 Phase 1). Mirror the funds surface but partition by `filer_type × aum_tier` rather than `equity_style_9box`, and include a portfolio-derived 9-box style attribution block. NAV is permanently absent (filers have no NAV time series); hedge ratios are Phase 3 (D.8.10). Plan reference: BWMACRO/docs/13f_pipeline_plan.md.\n"
    }
  ]
}