{
  "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 2006 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 2006-01-04 to present, 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"
      },
      "OAuth2ClientCredentials": {
        "type": "oauth2",
        "flows": {
          "clientCredentials": {
            "tokenUrl": "https://riskmodels.app/api/auth/token",
            "scopes": {
              "ticker-returns": "Access ticker returns and historical data",
              "risk-decomposition": "Access L3 risk decomposition",
              "batch-analysis": "Perform portfolio batch analysis",
              "factor-correlation": "Correlate stocks with macro factors (VIX, Bitcoin, Gold, etc.)",
              "macro-factor-series": "Download daily macro factor return series (no ticker)",
              "chat-risk-analyst": "Use AI risk analyst",
              "plaid:holdings": "Access Plaid-synced portfolio holdings",
              "*": "Full API access"
            }
          }
        }
      }
    },
    "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`.\nNone 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)."
          },
          "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"
          }
        }
      },
      "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
            }
          },
          "_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
          }
        }
      },
      "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
              }
            }
          }
        }
      },
      "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
              }
            }
          }
        }
      },
      "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"
          }
        }
      },
      "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"
              }
            }
          }
        }
      },
      "OAuth2TokenRequest": {
        "type": "object",
        "required": [
          "grant_type",
          "client_id",
          "client_secret"
        ],
        "properties": {
          "grant_type": {
            "type": "string",
            "enum": [
              "client_credentials"
            ],
            "description": "Must be \"client_credentials\""
          },
          "client_id": {
            "type": "string",
            "pattern": "^rm_(user|agent)_[a-z0-9_]{32,}$",
            "example": "rm_agent_live_abc123",
            "description": "API key prefix (first segment)"
          },
          "client_secret": {
            "type": "string",
            "format": "password",
            "example": "rm_agent_live_abc123_xyz789_checksum",
            "description": "Full API key"
          },
          "scope": {
            "type": "string",
            "example": "ticker-returns risk-decomposition",
            "description": "Space-separated list of requested scopes"
          }
        }
      },
      "OAuth2TokenResponse": {
        "type": "object",
        "properties": {
          "access_token": {
            "type": "string",
            "format": "jwt",
            "description": "JWT access token (use as Bearer token)"
          },
          "token_type": {
            "type": "string",
            "enum": [
              "Bearer"
            ]
          },
          "expires_in": {
            "type": "integer",
            "example": 900,
            "description": "Token lifetime in seconds (900 = 15 minutes)"
          },
          "scope": {
            "type": "string",
            "description": "Space-separated list of granted scopes"
          }
        }
      },
      "OAuth2Error": {
        "type": "object",
        "properties": {
          "error": {
            "type": "string",
            "enum": [
              "invalid_request",
              "invalid_client",
              "invalid_grant",
              "unsupported_grant_type",
              "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",
              "analyze_portfolio",
              "hedge_portfolio",
              "get_risk_decomposition"
            ],
            "description": "Available tools:\n- `riskmodels_list_endpoints`: List all available API endpoints with summaries, tags, and costs. No required inputs.\n- `riskmodels_get_capability`: Get detailed schema and documentation for a specific capability by ID (e.g. \"ticker-returns\", \"metrics\", \"l3-decomposition\"). Input: `{ id: string }`.\n- `riskmodels_get_schema`: Fetch the JSON response schema for a given endpoint path. Input: `{ path: string }`.\n- `analyze_portfolio`: Analyze a portfolio of ticker positions — returns per-ticker risk metrics (volatility, hedge ratios, explained risk) and portfolio-level aggregates. Input: `{ positions: Array<{ ticker: string, weight?: number }> }`.\n- `hedge_portfolio`: Compute optimal hedge notionals for a portfolio using ERM3 factor model. Returns L1/L2/L3 hedge amounts in USD. Input: `{ positions: Array<{ ticker: string, notional_usd: number }>, level?: \"l1\"|\"l2\"|\"l3\" }`.\n- `get_risk_decomposition`: Get monthly L3 factor risk decomposition time series for a ticker. Input: `{ ticker: string, from?: string, to?: string }`.\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
          },
          "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
              }
            }
          },
          "_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": {
    "/auth/token": {
      "post": {
        "summary": "Generate OAuth2 Access Token",
        "description": "OAuth 2.0 client credentials flow for machine-to-machine authentication. Exchange API credentials for a short-lived JWT token (15 minutes).\n\n**Use Cases:**\n- AI agents connecting to the API\n- Server-to-server integrations\n- npm package authentication\n- MCP client authentication\n",
        "operationId": "generateOAuthToken",
        "tags": [
          "Authentication"
        ],
        "security": [],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/OAuth2TokenRequest"
              },
              "example": {
                "grant_type": "client_credentials",
                "client_id": "rm_agent_live_abc123",
                "client_secret": "rm_agent_live_abc123_xyz789_checksum",
                "scope": "ticker-returns risk-decomposition"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Access token generated successfully",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OAuth2TokenResponse"
                },
                "example": {
                  "access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
                  "token_type": "Bearer",
                  "expires_in": 900,
                  "scope": "ticker-returns risk-decomposition batch-analysis"
                }
              }
            }
          },
          "400": {
            "description": "Invalid request (missing or invalid grant_type)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OAuth2Error"
                }
              }
            }
          },
          "401": {
            "description": "Invalid credentials",
            "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": []
          },
          {
            "OAuth2ClientCredentials": [
              "*"
            ]
          }
        ],
        "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": []
          },
          {
            "OAuth2ClientCredentials": [
              "*"
            ]
          }
        ],
        "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": []
          },
          {
            "OAuth2ClientCredentials": [
              "plaid:holdings"
            ]
          }
        ],
        "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": []
          },
          {
            "OAuth2ClientCredentials": [
              "plaid:holdings"
            ]
          }
        ],
        "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.02 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.02,
          "currency": "USD",
          "billing_code": "plaid_holdings_v2"
        },
        "security": [
          {
            "BearerAuth": []
          },
          {
            "OAuth2ClientCredentials": [
              "plaid:holdings"
            ]
          }
        ],
        "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.001 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.001,
          "currency": "USD",
          "billing_code": "metrics_v3"
        },
        "security": [
          {
            "BearerAuth": []
          },
          {
            "OAuth2ClientCredentials": [
              "ticker-returns"
            ]
          }
        ],
        "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"
                }
              }
            }
          }
        }
      }
    },
    "/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\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.001 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.001,
          "currency": "USD",
          "billing_code": "metrics_v3"
        },
        "security": [
          {
            "BearerAuth": []
          },
          {
            "OAuth2ClientCredentials": [
              "ticker-returns"
            ]
          }
        ],
        "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.005/call regardless of years pulled.\n",
        "operationId": "getTickerReturns",
        "tags": [
          "Risk Metrics"
        ],
        "x-pricing": {
          "capability_id": "ticker-returns",
          "tier": "baseline",
          "model": "per_request",
          "cost_usd": 0.005,
          "currency": "USD",
          "billing_code": "ticker_returns_v2"
        },
        "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"
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/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.02 per request.\n",
        "operationId": "getL3Decomposition",
        "tags": [
          "Risk Metrics"
        ],
        "x-pricing": {
          "capability_id": "l3-decomposition",
          "tier": "premium",
          "model": "per_request",
          "cost_usd": 0.02,
          "currency": "USD",
          "billing_code": "l3_decomposition_v2"
        },
        "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"
                }
              }
            }
          }
        }
      }
    },
    "/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.002 per ticker. 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.002,
          "currency": "USD",
          "billing_code": "factor_correlation_v1"
        },
        "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.002,
          "currency": "USD",
          "billing_code": "factor_correlation_v1"
        },
        "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.001 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.001,
          "currency": "USD",
          "billing_code": "macro_factor_series_v1"
        },
        "security": [
          {
            "BearerAuth": []
          },
          {
            "OAuth2ClientCredentials": [
              "macro-factor-series"
            ]
          }
        ],
        "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.001,
          "currency": "USD",
          "billing_code": "rankings_v3"
        },
        "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.001,
          "currency": "USD",
          "billing_code": "rankings_v3"
        },
        "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.001,
          "currency": "USD",
          "billing_code": "rankings_v3"
        },
        "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.005,
          "min_charge": 0.01,
          "currency": "USD",
          "billing_code": "batch_analysis_v3"
        },
        "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"
                }
              }
            }
          }
        }
      }
    },
    "/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.03 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.03,
          "currency": "USD",
          "billing_code": "portfolio_risk_index_v2"
        },
        "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:** $0.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": 0.25,
          "currency": "USD",
          "billing_code": "risk_snapshot_pdf_v1"
        },
        "security": [
          {
            "BearerAuth": []
          },
          {
            "OAuth2ClientCredentials": [
              "*"
            ]
          }
        ],
        "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 ($0.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": 0.25,
          "currency": "USD",
          "billing_code": "risk_snapshot_pdf_v1"
        },
        "security": [
          {
            "BearerAuth": []
          },
          {
            "OAuth2ClientCredentials": [
              "*"
            ]
          }
        ],
        "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"
                }
              }
            }
          }
        }
      }
    },
    "/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 $0.25 pricing as `POST /portfolio/risk-snapshot` with `format=pdf`. Cached 24h per authenticated user and ticker.\n**Billing:** $0.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": 0.25,
          "currency": "USD",
          "billing_code": "risk_snapshot_pdf_v1"
        },
        "security": [
          {
            "BearerAuth": []
          },
          {
            "OAuth2ClientCredentials": [
              "*"
            ]
          }
        ],
        "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."
          }
        }
      }
    },
    "/tickers": {
      "get": {
        "summary": "Ticker universe search",
        "description": "List tickers in the universe or search by name/symbol. Billed per request ($0.001). 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.001,
          "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": []
          },
          {
            "OAuth2ClientCredentials": [
              "*"
            ]
          }
        ],
        "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."
                        }
                      }
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/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.003,
          "currency": "USD",
          "billing_code": "cli_query_v1"
        },
        "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.002/call.\n",
        "operationId": "getTelemetry",
        "tags": [
          "Utility"
        ],
        "x-pricing": {
          "capability_id": "telemetry-metrics",
          "tier": "baseline",
          "model": "per_request",
          "cost_usd": 0.002,
          "currency": "USD",
          "billing_code": "telemetry_v2"
        },
        "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_v2",
          "input_cost_per_1k": 0.001,
          "output_cost_per_1k": 0.002
        },
        "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."
                  }
                }
              }
            }
          }
        },
        "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 OPENAI_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
            }
          }
        ],
        "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
            }
          }
        ],
        "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 / sponsor in the committed cross-repo catalog (mirror of `Funds_DAG/configs/etf_universe.yaml` at `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; 20 sponsor exact / 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, partial name, or sponsor. 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/1.0"
                    },
                    "results": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "bw_etf_id": {
                            "type": "string",
                            "example": "BW-ETF-IVV"
                          },
                          "ticker": {
                            "type": "string",
                            "example": "IVV"
                          },
                          "sponsor": {
                            "type": "string",
                            "example": "ishares"
                          },
                          "name": {
                            "type": "string"
                          },
                          "product_url": {
                            "type": "string",
                            "nullable": true
                          },
                          "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`, …).\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"
            },
            "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"
                    },
                    "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.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "404": {
            "description": "Unknown benchmark, or no surface for the subject/benchmark.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/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.01,
          "billing_code": "fund_snapshot_json_v1"
        },
        "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": 0.25,
          "billing_code": "fund_snapshot_pdf_v1"
        },
        "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.005,
          "billing_code": "style_cohort_snapshot_json_v1"
        },
        "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.1,
          "billing_code": "style_cohort_snapshot_pdf_v1"
        },
        "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.005,
          "billing_code": "style_cohort_portfolio_history_v1"
        },
        "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.005,
          "billing_code": "style_cohort_holdings_v1"
        },
        "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.005,
          "billing_code": "style_cohort_metrics_v1"
        },
        "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.005,
          "billing_code": "style_cohort_rankings_v1"
        },
        "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
            }
          }
        ],
        "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.005,
          "billing_code": "fund_hedge_v1"
        },
        "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.005,
          "billing_code": "fund_holdings_v1"
        },
        "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.005,
          "billing_code": "fund_portfolio_history_v1"
        },
        "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.005,
          "billing_code": "fund_nav_history_v1"
        },
        "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.005,
          "billing_code": "fund_metrics_v1"
        },
        "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. Free for users (no per-request cost) — per-filer follow-up calls are metered.\n",
        "operationId": "searchFilers",
        "tags": [
          "13F Filers"
        ],
        "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": 500
            }
          }
        ],
        "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.005,
          "billing_code": "filer_metrics_v1"
        },
        "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 current holdings at the filer's latest teo. 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).\n",
        "operationId": "getFilerHoldings",
        "tags": [
          "13F Filers"
        ],
        "x-pricing": {
          "capability_id": "filer-holdings",
          "tier": "baseline",
          "cost_usd": 0.005,
          "billing_code": "filer_holdings_v1"
        },
        "parameters": [
          {
            "name": "bw_filer_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "default": 25,
              "maximum": 1000
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Top-N holdings at the latest teo.",
            "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 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).\n",
        "operationId": "getFilerPortfolioHistory",
        "tags": [
          "13F Filers"
        ],
        "x-pricing": {
          "capability_id": "filer-portfolio-history",
          "tier": "baseline",
          "cost_usd": 0.005,
          "billing_code": "filer_portfolio_history_v1"
        },
        "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": "Portfolio history rows.",
            "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}/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.01,
          "billing_code": "filer_snapshot_json_v1"
        },
        "parameters": [
          {
            "name": "bw_filer_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "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.05,
          "billing_code": "filer_snapshot_pdf_v1"
        },
        "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"
    }
  ]
}