{
  "openapi": "3.0.3",
  "info": {
    "title": "RiskModels API",
    "version": "3.0.0-agent",
    "description": "Precision equity risk intelligence platform with AI agent integration.\nInstitutional-grade equity risk analysis for AI agents and quantitative applications. Provides daily factor hedge ratios, explained-risk decompositions, and portfolio-level risk attribution for ~3,000 US equities.\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"
    }
  ],
  "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"
          }
        }
      },
      "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 (V3 security_history fields).",
        "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"
          },
          "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",
            "example": "SOXX"
          },
          "subsector_etf": {
            "type": "string",
            "example": "XSD"
          },
          "universe": {
            "type": "string",
            "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"
              }
            }
          }
        }
      },
      "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
          }
        }
      }
    }
  },
  "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\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",
        "operationId": "getAIPluginManifest",
        "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",
        "description": "Comprehensive data handling and privacy disclosure for AI marketplace compliance. Documents encryption, retention, third-party sharing, and regulatory compliance.\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",
        "description": "Model Context Protocol server connection manifest. Enables AI model clients to discover and connect to the RiskModels MCP server.\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": "Latest risk metrics snapshot",
        "description": "Returns 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"
                }
              }
            }
          }
        }
      }
    },
    "/ticker-returns": {
      "get": {
        "summary": "Daily returns time series with rolling hedge ratios",
        "description": "Returns a daily time series of gross stock returns (`returns_gross`) and 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. 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)",
        "description": "DEPRECATED — route not currently implemented. Use `/ticker-returns` for the equivalent data (daily gross returns plus hedge ratios). Returns 404 today; will be removed from the spec once the SDK drops its `get_returns` wrapper.\n",
        "deprecated": true,
        "operationId": "getReturns",
        "tags": [
          "Risk Metrics",
          "Deprecated"
        ],
        "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": "AAPL"
          },
          {
            "$ref": "#/components/parameters/FormatQueryTabular"
          }
        ],
        "responses": {
          "200": {
            "description": "Daily gross returns. JSON uses a parallel `returns_gross` array (see schema). Parquet/CSV use a long table `#/components/schemas/GrossReturnDailyRow` (`date`, `returns_gross` per row).\n",
            "headers": {
              "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; columns `date`, `returns_gross` per `#/components/schemas/GrossReturnDailyRow`.\n"
              },
              "text/csv": {
                "schema": {
                  "type": "string"
                },
                "description": "UTF-8 CSV with header `date,returns_gross`.\n",
                "example": "date,returns_gross\n2025-03-20,0.0123\n"
              },
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "symbol": {
                      "type": "string"
                    },
                    "ticker": {
                      "type": "string"
                    },
                    "periodicity": {
                      "type": "string"
                    },
                    "returns_gross": {
                      "type": "array",
                      "items": {
                        "type": "number",
                        "nullable": true
                      }
                    },
                    "_metadata": {
                      "$ref": "#/components/schemas/RiskMetadata"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Missing ticker."
          },
          "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"
                }
              }
            }
          }
        }
      }
    },
    "/etf-returns": {
      "get": {
        "summary": "Daily gross returns time series (ETF)",
        "description": "DEPRECATED — route not currently implemented (returns 404). ETF returns will be served from the GCP zarr store in a follow-up; until then there is no equivalent endpoint (ETFs are excluded from `/ticker-returns` because they are not L3-decomposed).\n",
        "deprecated": true,
        "operationId": "getEtfReturns",
        "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": "etf",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string"
            },
            "example": "SPY",
            "description": "ETF ticker symbol."
          },
          {
            "$ref": "#/components/parameters/FormatQueryTabular"
          }
        ],
        "responses": {
          "200": {
            "description": "Daily ETF gross returns. JSON exposes parallel `dates` and `returns_gross` arrays. Parquet/CSV flatten to `#/components/schemas/GrossReturnDailyRow`.\n",
            "headers": {
              "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; columns `date`, `returns_gross` per `#/components/schemas/GrossReturnDailyRow`.\n"
              },
              "text/csv": {
                "schema": {
                  "type": "string"
                },
                "description": "UTF-8 CSV with header `date,returns_gross`.\n",
                "example": "date,returns_gross\n2025-03-20,0.0012\n"
              },
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "symbol": {
                      "type": "string"
                    },
                    "ticker": {
                      "type": "string"
                    },
                    "periodicity": {
                      "type": "string"
                    },
                    "dates": {
                      "type": "array",
                      "items": {
                        "type": "string"
                      }
                    },
                    "returns_gross": {
                      "type": "array",
                      "items": {
                        "type": "number",
                        "nullable": true
                      }
                    },
                    "_metadata": {
                      "$ref": "#/components/schemas/RiskMetadata"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Missing etf."
          },
          "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": "ETF 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"
                }
              }
            }
          }
        }
      }
    },
    "/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": "Multi-ticker batch analysis",
        "description": "Fetch 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**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": "Portfolio risk snapshot (bundled PDF or JSON)",
        "description": "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"
                }
              }
            }
          }
        }
      }
    },
    "/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
                }
              }
            }
          }
        }
      }
    }
  },
  "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."
    }
  ]
}