riskmodels-app 13F Filers API

Compute-bearing 13F filer endpoints (D.8 Phase 1). Mirror the funds surface but partition by `filer_type × aum_tier` rather than `equity_style_9box`, and include a portfolio-derived 9-box style attribution block. NAV is permanently absent (filers have no NAV time series); hedge ratios are Phase 3 (D.8.10). Plan reference: BWMACRO/docs/13f_pipeline_plan.md.

Operations 7

GET /13f/filers/search 13F filer search #
GET /13f/filers/{bw_filer_id} Latest 13F filer metrics #
GET /13f/filers/{bw_filer_id}/holdings 13F filer top holdings #
GET /13f/filers/{bw_filer_id}/portfolio 13F filer portfolio history #
GET /13f/filers/{bw_filer_id}/concentration 13F filer concentration summary #
GET /13f/filers/{bw_filer_id}/snapshot 13F filer composed snapshot (JSON) #
GET /13f/filers/{bw_filer_id}/snapshot.pdf 13F filer F1 tearsheet (PDF) #

Work with this as data

Every API here is available over the APIs.io API and to AI agents over MCP.

MCP server

One button, every client — Claude, Cursor, VS Code and the rest.

https://apis.io/mcp

Tools for apis

7 MCP tools reach this
  • find_apisBrowse and filter every API in the catalog.
  • get_api_artifactsOne API's artifacts, grouped by type.
  • get_openapiThe primary OpenAPI for this API.
  • find_similar_apisAPIs that look like this one.
  • apis_io_searchSTART HERE — APIs, providers and tags for one query, each with its total.
  • resolveTurn a domain, URL or GitHub org into the provider it belongs to.
  • find_cohortsEvery scored population of providers in the catalog.
All 92 tools →

Call it yourself

curl for this page
This API
curl "https://apis.io/api/v1/apis/riskmodels-app:riskmodels-app-13f-filers-api"
All apis
curl "https://apis.io/api/v1/apis?limit=25"

Discovery needs no key. Ratings and market analysis are Pro.

Get an API key

Free tier, no form to fill in. Signing in shares your email address with us — we store it to create your key and to recognise you if you sign in with another provider. See our Privacy Policy and Terms.

A second provider on the same verified email joins the account you already have.

OpenAPI Specification

riskmodels-app-13f-filers-api-openapi.yml Raw ↑
openapi: 3.2.0
info:
  title: RiskModels 13F Filers API
  version: 3.0.0-agent
  description: 'POST /decompose returns variance shares and ETF hedge ratios for any US equity, separating factor exposure from the residual bet so you can hedge what you don''t want. Daily history to 2000 across ~3,000 US equities.

    Precision equity risk intelligence platform with AI agent integration.


    **New in v3.0.0-agent:**

    - OAuth2 client credentials flow for machine-to-machine auth

    - Plaid Investments integration for live portfolio sync

    - MCP (Model Context Protocol) server for AI agent tools

    - Compliance manifests for AI marketplace integration

    - Per-key rate limiting and scoped access control


    **Data**: Universe `uni_mc_3000` (top ~3,000 US stocks by market cap), date range 2000 to present (deep panel; stock-specific skill series from ~2001 after warmup), updated daily end-of-day. Model outputs are computed by the ERM3 regression system (Huber/Ridge), with Zarr v2 archives on Google Cloud Storage for research pipelines. The live API reads primarily from Supabase **V3** tables: long-form `security_history` (returns, volatility, L1/L2/L3 hedge ratios and explained-risk metrics), wide latest rows in `security_history_latest`, identity in `symbols`, plus supporting surfaces such as `trading_calendar`, `erm3_landing_chart_cache`, and `macro_factors`. Cross-sectional **rankings** (`GET /rankings/*`) are served from precomputed rank metric keys in `security_history`, not a separate `erm3_rankings` table. Hedge ratios (`*_hr`) are **dollar_ratio** ETF notionals per $1 stock and are distinct from regression **betas** (`l1_mkt_beta`, `l2_sec_beta`, `l3_sub_beta`), which are dimensionless coefficients from the hierarchical L1/L2/L3 regression. Both are exposed under `MetricsV3` and on `security_history_latest`. See `SEMANTIC_ALIASES.md` for the full HR-vs-beta convention.


    **Authentication Methods:**

    1. Session cookies (web app)

    2. API Key/Secret via Bearer token (external integrations)

    3. OAuth2 client credentials (AI agents, npm package)


    **Security Features:**

    - GCP KMS envelope encryption for portfolio data

    - Cryptographic shredding on key deletion

    - HMAC-SHA256 webhook verification

    - Row-level security (RLS) via Supabase


    **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`.

    **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.

    **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.'
  contact:
    name: RiskModels Support
    email: service@riskmodels.app
    url: https://riskmodels.app
  termsOfService: https://riskmodels.app/legal
  license:
    name: Commercial
    url: https://riskmodels.app/legal
servers:
- url: https://riskmodels.app/api
  description: Production — all metered endpoints (`/metrics`, `/snapshot`, `/decompose`, `/batch/analyze`, `/portfolio/*`, etc.).
- url: https://riskmodels.app
  description: Discovery origin — `/.well-known/*` manifests (`mcp.json`, `ai-plugin.json`, `agentic-disclosure.json`) live at the site origin without the `/api` prefix. Generated clients that hard-code the first server entry must strip `/api` before requesting these paths.
security:
- BearerAuth: []
tags:
- name: 13F Filers
  description: 'Compute-bearing 13F filer endpoints (D.8 Phase 1). Mirror the funds surface but partition by `filer_type × aum_tier` rather than `equity_style_9box`, and include a portfolio-derived 9-box style attribution block. NAV is permanently absent (filers have no NAV time series); hedge ratios are Phase 3 (D.8.10). Plan reference: BWMACRO/docs/13f_pipeline_plan.md.'
paths:
  /13f/filers/search:
    get:
      summary: 13F filer search
      description: 'Discovery surface for 13F filers. Search by name, CIK, or LEI; filter by filer_type / aum_tier; optionally restrict to filers passing the modelability gate. Per-filer follow-up calls under `/13f/filers/{bw_filer_id}/*` are metered.


        **Authentication:** None — this endpoint is public. Like `/funds/search`, it is wrapped in `withBilling` with `skipBilling: true`, which bypasses key validation entirely.

        **Billing:** Free

        **Rate Limit:** 60 requests/minute per IP (`FILER_SEARCH_IP_RPM`). `limit` is capped at 100 rows.'
      operationId: searchFilers
      tags:
      - 13F Filers
      security: []
      x-pricing:
        capability_id: filer-search
        tier: baseline
        cost_usd: 0
        billing_code: filer_search_v1
      parameters:
      - name: q
        in: query
        required: false
        schema:
          type: string
      - name: filer_type
        in: query
        required: false
        schema:
          type: string
      - name: aum_tier
        in: query
        required: false
        schema:
          type: string
      - name: modelable_only
        in: query
        required: false
        schema:
          type: boolean
      - name: limit
        in: query
        required: false
        schema:
          type: integer
          default: 50
          maximum: 100
      responses:
        '200':
          description: Filer search results.
          content:
            application/json:
              schema:
                type: object
                properties:
                  results:
                    type: array
                    items:
                      type: object
        '429':
          description: Rate limit exceeded.
  /13f/filers/{bw_filer_id}:
    get:
      summary: Latest 13F filer metrics
      description: Latest knowledge-mode portfolio metrics for a single 13F filer. Reads `public.filers` + `public.filer_portfolios_latest`. Returns diagnostics, AUM (total + in-ERM3), ERM3-coverage modelability inputs, and portfolio-derived 9-box style attribution. Return components are NULL until D.8 Phase 2. NAV is permanently absent — filers have no NAV time series.
      operationId: getFilerMetrics
      tags:
      - 13F Filers
      x-pricing:
        capability_id: filer-metrics
        tier: baseline
        cost_usd: 0.02
        billing_code: filer_metrics_v2
      parameters:
      - name: bw_filer_id
        in: path
        required: true
        schema:
          type: string
        example: BW-FILER-CIK0001067983
      responses:
        '200':
          description: Latest filer metrics.
          headers:
            X-Data-As-Of:
              schema:
                type: string
                format: date
            X-Data-Filing-Date:
              schema:
                type: string
                format: date
            X-Risk-Model-Version:
              schema:
                type: string
            X-API-Cost-USD:
              schema:
                type: string
          content:
            application/json:
              schema:
                type: object
        '402':
          description: Insufficient prepaid balance.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/InsufficientBalance'
        '404':
          description: Filer not found.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '429':
          description: Rate limit exceeded.
  /13f/filers/{bw_filer_id}/holdings:
    get:
      summary: 13F filer top holdings
      description: 'Top-N holdings at the filer''s latest teo — or, with `as_of`, at the latest quarter *known* by that date (knowledge mode: selection on `filing_date <= as_of`; falls back to `report_date <= as_of` on panels without per-quarter filing dates, with the basis echoed as `as_of_basis`). Reads per-filer ds_ph.zarr from GCS. Each holding carries `security_id` (post-D.8.1 = bw_sym_id; pre-migration = a raw 9-char security identifier), `adj_mv`, and `weight` (fraction of total in-portfolio AUM). Bi-temporal stamps are body fields — `report_date` (valid time, = teo) and `filing_date` (knowledge time, EDGAR date_filed of the surviving submission; null until the per-quarter data fill lands) — mirrored on the X-Data-As-Of / X-Data-Filing-Date headers.


        The surviving submission''s filing identity is reported alongside them, at the response root (one accession state per selected quarter, not per holding): `accession_number`, `filing_type` (the SEC **form type** — `13F-HR`, `13F-HR/A`, `13F-NT` — not the report quarter) and `amendment_type` (cover-page semantics: `ORIGINAL`, `RESTATEMENT`, `NEW_HOLDINGS`, `UNKNOWN`). Use them to tell an original filing from an amendment, and a restatement of previously reported positions from an amendment that only adds new ones. All three are `null` on panels published before the accession-vintage stores; `amendment_type` is never inferred from a `/A` suffix, so a null means "not classified upstream", which is distinct from the `UNKNOWN` value.'
      operationId: getFilerHoldings
      tags:
      - 13F Filers
      x-pricing:
        capability_id: filer-holdings
        tier: baseline
        cost_usd: 0.02
        billing_code: filer_holdings_v2
      parameters:
      - name: bw_filer_id
        in: path
        required: true
        schema:
          type: string
        description: 'Canonical filer id (BW-FILER-CIK{cik}). A bare or unpadded CIK (`1067983`, `0001067983`, `CIK1067983`, `BW-FILER-CIK1067983`) resolves to the same filer. Composite entities (BW-SYNTH-*) are served by the same route; registry-only fields (cik, filer_type, aum_tier) return null.

          '
      - name: limit
        in: query
        required: false
        schema:
          type: integer
          default: 25
          maximum: 1000
        description: Rows per page (at most 1000).
      - name: offset
        in: query
        required: false
        schema:
          type: integer
          default: 0
          minimum: 0
        description: 'Page past the first `limit` rows. Rows are ranked by `adj_mv` descending and a page is a slice of that ranking; follow `next_offset` until it is null to read the whole book (served to a depth of 50,000 rows; `truncated_at_max_depth` says when a book is deeper). Ties in `adj_mv` are ordered by `security_id`.

          '
      - name: as_of
        in: query
        required: false
        schema:
          type: string
          format: date
        description: 'Knowledge-mode date: serve the latest quarter whose filing was public on or before this date. 404 when nothing was known yet.

          '
      responses:
        '200':
          description: 'Top-N holdings at the selected teo, with `report_date`, `filing_date`, the selected accession''s filing identity (`accession_number`, `filing_type`, `amendment_type`), and (under `as_of`) `as_of_basis`.

            '
          content:
            application/json:
              schema:
                type: object
                additionalProperties: true
                properties:
                  report_date:
                    type: string
                    format: date
                    description: 'Valid time — the 13F reporting period end (= teo) the holdings describe.

                      '
                  filing_date:
                    type:
                    - string
                    - 'null'
                    format: date
                    description: 'Knowledge time — EDGAR date_filed of the surviving submission for this quarter. Null on panels without per-quarter filing dates.

                      '
                  as_of_basis:
                    type: string
                    enum:
                    - filing_date
                    - report_date
                    description: 'Which axis resolved an `as_of` selection.

                      '
                  accession_number:
                    type:
                    - string
                    - 'null'
                    description: 'EDGAR accession number of the surviving submission for the selected quarter — the filing whose positions this panel reports. Null on panels published before the accession-vintage stores.

                      '
                    example: 0000950123-24-008496
                  filing_type:
                    type:
                    - string
                    - 'null'
                    description: 'SEC **submission/form type** of that accession — not the report quarter. `13F-HR` is an original holdings report, `13F-HR/A` an amendment to one, `13F-NT` a notice filing. Null when the form type is not published for this panel.

                      '
                    example: 13F-HR/A
                  amendment_type:
                    type:
                    - string
                    - 'null'
                    enum:
                    - ORIGINAL
                    - RESTATEMENT
                    - NEW_HOLDINGS
                    - UNKNOWN
                    - null
                    description: 'Cover-page amendment semantics of that accession, as classified upstream. `ORIGINAL` — not an amendment. `RESTATEMENT` — the amendment replaces the previously reported holdings in full. `NEW_HOLDINGS` — it adds holdings on top of the prior filing. `UNKNOWN` — an amendment whose cover page did not declare which. Null means no upstream classification is published for this panel, which is *not* the same as `UNKNOWN`; it is never inferred from a `/A` suffix.

                      '
                    example: RESTATEMENT
                  offset:
                    type: integer
                    description: First row of this page in the adj_mv ranking.
                  next_offset:
                    type:
                    - integer
                    - 'null'
                    description: Offset of the next page; null on the last page.
                  truncated_at_max_depth:
                    type: boolean
                    description: 'The book has more than 50,000 rows; pages stop at that depth and `next_offset` is null there.

                      '
                  holdings:
                    type: array
                    items:
                      type: object
                      additionalProperties: true
        '402':
          description: Insufficient prepaid balance.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/InsufficientBalance'
        '404':
          description: Filer not found, or no holdings panel available.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '429':
          description: Rate limit exceeded.
  /13f/filers/{bw_filer_id}/portfolio:
    get:
      summary: 13F filer portfolio history
      description: Per-filer portfolio time series from per-filer ds_portfolio.zarr. One row per teo (quarter-end) with diagnostics, AUM, ERM3 coverage, and portfolio style attribution. Return components are NULL until D.8 Phase 2 (the security-master ↔ ERM3 attribution bridge). Each row carries its `filing_date` (knowledge-time stamp; null until the per-quarter data fill lands). `as_of` filters to rows known by that date (`filing_date <= as_of`, falling back to `teo <= as_of` on panels without filing dates; basis echoed as `as_of_basis`).
      operationId: getFilerPortfolioHistory
      tags:
      - 13F Filers
      x-pricing:
        capability_id: filer-portfolio-history
        tier: baseline
        cost_usd: 0.02
        billing_code: filer_portfolio_history_v2
      parameters:
      - name: bw_filer_id
        in: path
        required: true
        schema:
          type: string
      - name: start_date
        in: query
        required: false
        schema:
          type: string
          format: date
      - name: end_date
        in: query
        required: false
        schema:
          type: string
          format: date
      - name: as_of
        in: query
        required: false
        schema:
          type: string
          format: date
        description: 'Knowledge-mode date: keep only quarters whose filings were public on or before this date. 404 when nothing was known yet.

          '
      responses:
        '200':
          description: 'Portfolio history rows (each with `filing_date`). Additive quality fields (2026-10-06), keyed on the book each row was built from (`ds_ph`''s accession for the quarter): per row `original_filing_date` (first EDGAR filing for the quarter), `filing_date_is_amendment` (the row''s book is not that first filing), `n_amendments`, `accession_number`, `book_complete`, `mapped_share` (share of the book''s reported value whose security identifiers resolved), `erm3_universe_share` (= `coverage_in_erm3`), `is_partial_period` (the row''s teo → next quarter-end window ends after the data''s `returns_window_end`), `is_stub` (returns with no holdings snapshot), `repair_status` (most severe of `rows_quarantined` > `amendment_restated` > `units_repaired` > `reparsed`, `none`, or null when the repair ledger is unavailable), `repair_rows_affected` and `repair_detail` (rows per repair kind). At the root: `missing_quarters` (quarter-ends inside the window with no book), `data_vintage` (store schema version, identifier-window fingerprint, returns window end) and `quality_sources` (`ok` / `unpublished` / `error` for the vintage store, repair ledger and store attributes).

            '
          content:
            application/json:
              schema:
                type: object
        '402':
          description: Insufficient prepaid balance.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/InsufficientBalance'
        '404':
          description: Filer not found, or no portfolio history available.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '429':
          description: Rate limit exceeded.
  /13f/filers/{bw_filer_id}/concentration:
    get:
      summary: 13F filer concentration summary
      description: Quarter-end concentration panel from per-filer ds_portfolio.zarr. Returns median and latest effective N, top-5 / top-10 weight share, and weight HHI over an optional date window (same underlying series as /portfolio, summarized for diligence).
      operationId: getFilerConcentration
      tags:
      - 13F Filers
      x-pricing:
        capability_id: filer-concentration
        tier: baseline
        cost_usd: 0.02
        billing_code: filer_concentration_v2
      parameters:
      - name: bw_filer_id
        in: path
        required: true
        schema:
          type: string
      - name: start_date
        in: query
        required: false
        schema:
          type: string
          format: date
      - name: end_date
        in: query
        required: false
        schema:
          type: string
          format: date
      responses:
        '200':
          description: Concentration summary (median + latest).
          content:
            application/json:
              schema:
                type: object
        '402':
          description: Insufficient prepaid balance.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/InsufficientBalance'
        '404':
          description: Filer not found, or no concentration panel available.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '429':
          description: Rate limit exceeded.
  /13f/filers/{bw_filer_id}/snapshot:
    get:
      summary: 13F filer composed snapshot (JSON)
      description: 'Single-call composed snapshot for a 13F filer: registry + latest metrics + top-25 holdings + 12mo portfolio history + cohort ranks (filer_type and aum_tier partitions) + portfolio-derived 9-box style attribution + ERM3 coverage diagnostics + modelability flag. NAV is intentionally absent (`_metadata.nav_applicable: false`) — filers have no NAV time series. Adds `erm3_decomposition` from filer `ds_returns_monthly.zarr` (monthly L3 rows, variance share attrs, latest-month waterfall) when present (D.8.22). Holdings may include latest daily L3 ER shares per `bw_sym_id` from `security_history_latest`. `hedge_sleeve` is populated when filer `ds_hr.zarr` exists (D.8.10 Phase 3); otherwise null.'
      operationId: getFilerSnapshot
      tags:
      - 13F Filers
      x-pricing:
        capability_id: filer-snapshot-json
        tier: premium
        cost_usd: 0.05
        billing_code: filer_snapshot_json_v2
      parameters:
      - name: bw_filer_id
        in: path
        required: true
        schema:
          type: string
        description: 'Canonical filer id (BW-FILER-CIK{cik}). Composite entities (BW-SYNTH-*) are served by the same route; the snapshot adds entity_kind: synthetic_composite, evidence_class: reconstructed, recipe, and composition coverage; registry-only fields return null.

          '
      responses:
        '200':
          description: Composed FilerSnapshot.
          content:
            application/json:
              schema:
                type: object
        '402':
          description: Insufficient prepaid balance.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/InsufficientBalance'
        '404':
          description: Filer not found.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '429':
          description: Rate limit exceeded.
  /13f/filers/{bw_filer_id}/snapshot.pdf:
    get:
      summary: 13F filer F1 tearsheet (PDF)
      description: Rendered 1-page F1 tearsheet PDF for a 13F filer. Same content as /snapshot (JSON). NAV section is absent by design. Server-rendered via headless Chromium (Playwright); requires PLAYWRIGHT_PDF_ENABLED=true on the API runtime.
      operationId: getFilerSnapshotPdf
      tags:
      - 13F Filers
      x-pricing:
        capability_id: filer-snapshot-pdf
        tier: premium
        cost_usd: 0.25
        billing_code: filer_snapshot_pdf_v2
      parameters:
      - name: bw_filer_id
        in: path
        required: true
        schema:
          type: string
      responses:
        '200':
          description: PDF tearsheet bytes.
          content:
            application/pdf:
              schema:
                type: string
                format: binary
        '402':
          description: Insufficient prepaid balance.
        '404':
          description: Filer not found.
        '503':
          description: PDF rendering disabled (PLAYWRIGHT_PDF_ENABLED unset).
components:
  schemas:
    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
    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
        history_starts:
          type: string
          format: date
          description: 'Earliest non-null daily close or return date for the resolved security. Present only when a historical request ends before the security''s own history begins.

            '
        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
  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_...`

        '
    OAuth2AuthorizationCode:
      type: oauth2
      description: 'OAuth 2.0 authorization code flow with PKCE (S256), used by MCP clients (Claude Desktop, Cursor, ChatGPT Developer Mode, Grok) that self-register via `POST /api/oauth/register`. Public clients only — no `client_secret` is issued and `token_endpoint_auth_method` is `none`.


        The access token this flow returns **is** an `rm_user_*` API key (1 h TTL, 30 d rotating refresh token), so it authenticates every `BearerAuth` operation below, not just `/api/mcp/sse`. Scope is currently informational: the API records the presented scope for telemetry but authorises by key validity and account balance, not by scope.


        Discovery metadata: `/.well-known/oauth-authorization-server`.

        '
      flows:
        authorizationCode:
          authorizationUrl: https://riskmodels.app/oauth/authorize
          tokenUrl: https://riskmodels.app/api/oauth/token
          refreshUrl: https://riskmodels.app/api/oauth/token
          scopes:
            mcp:read: Read access to risk analytics (the only scope the authorization server advertises)