riskmodels-app Discovery API

Service discovery and capability manifests.

Operations 5

GET /.well-known/ai-plugin.json OpenAI GPT Plugin Manifest #
GET /.well-known/mcp.json MCP Server Manifest #
GET /artifacts/capability Artifact render capability #
GET /artifacts/as-of Artifact pre-rendered vintages #
GET /pricing Public capability pricing manifest #

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-discovery-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-discovery-api-openapi.yml Raw ↑
openapi: 3.2.0
info:
  title: RiskModels Discovery 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: Discovery
  description: Service discovery and capability manifests.
paths:
  /.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.

        **Discovery URL:** Served at the **site origin** (`https://riskmodels.app/.well-known/ai-plugin.json`), not under `/api`. Generated clients that combine the first `servers[0].url` with this path must strip `/api` first.'
      operationId: getAIPluginManifest
      servers:
      - url: https://riskmodels.app
        description: Site origin (discovery)
      tags:
      - 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/mcp.json:
    get:
      summary: MCP Server Manifest
      servers:
      - url: https://riskmodels.app
        description: Site origin (discovery)
      description: 'Model Context Protocol server connection manifest. Enables AI model clients to discover and connect to the RiskModels MCP server.

        **Discovery URL:** Served at the **site origin** (`https://riskmodels.app/.well-known/mcp.json`), not under `/api`.'
      operationId: getMCPManifest
      tags:
      - 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'
  /artifacts/capability:
    get:
      summary: Artifact render capability
      description: 'The verified (slug, subject_kind) pairs render-svc can serve, each slug''s applicable render params, and the pairs that were measured and refused.

        Derived from the audited capability table on every request, so a pair that was never verified cannot be advertised. Answers "what renders for this subject?" before a client mounts anything — previously the only way to learn the answer was to request something that failed, because the reconciled table appeared solely inside error payloads.

        **Authentication:** None. Discovery only: it names renderers and subject kinds and returns no subject content, no holdings and no numbers.

        **Billing:** Free.'
      operationId: getArtifactCapability
      tags:
      - Discovery
      x-pricing:
        capability_id: artifact-capability
        tier: baseline
        model: per_request
        cost_usd: 0
        currency: USD
        billing_code: artifact_capability
      security: []
      parameters:
      - name: subject_kind
        in: query
        required: false
        schema:
          type: string
          enum:
          - fund
          - etf
          - filer_13f
          - cohort
          - stock
          - client_portfolio
        description: 'Narrow `pairs` to one subject kind. `unavailable` is never narrowed: a caller needs to tell "measured and refused" from "never asked".

          '
        example: fund
      - name: slug
        in: query
        required: false
        schema:
          type: string
        description: Narrow `pairs` to one artifact slug.
        example: cumulative_return_strip
      responses:
        '200':
          description: 'Capability document. A narrowing query that matches nothing returns 200 with an empty `pairs` list, not 404.

            '
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ArtifactCapabilityResponse'
        '400':
          description: Unknown subject_kind.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
  /artifacts/as-of:
    get:
      summary: Artifact pre-rendered vintages
      description: 'The pre-rendered `as_of` dates for one (slug, subject_id), with the formats stored under each date and their object URIs. Read-only: no render is triggered by asking.

        Pre-rendered subject kinds (filers, cohorts) have no live loader, so a caller must know which dates exist. For cohort subjects `as_of=latest` on the render call resolves to the newest date listed here; for filers pass one of these dates explicitly. Both spellings of a filer id are searched and merged.

        **Authentication:** API key or session. Unmetered.

        **Billing:** Free.'
      operationId: getArtifactAsOf
      tags:
      - Discovery
      x-pricing:
        capability_id: artifact-as-of
        tier: baseline
        model: per_request
        cost_usd: 0
        currency: USD
        billing_code: artifact_as_of_v1
      parameters:
      - name: slug
        in: query
        required: true
        schema:
          type: string
          pattern: ^[a-z][a-z0-9_]*$
        description: Artifact slug.
        example: risk_dna_stacked
      - name: subject_id
        in: query
        required: true
        schema:
          type: string
        description: 'Subject id with a known prefix (BW-COHORT-, BW-FILER-, BW-FUND-, BW-STOCK-, BW-PORTFOLIO-, BW-ETF-).

          '
        example: BW-COHORT-RES-MAG7
      - name: version
        in: query
        required: false
        schema:
          type: string
          pattern: ^v\d+$
          default: v1
        description: Artifact version tag.
      responses:
        '200':
          description: At least one vintage is pre-rendered.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ArtifactAsOfResponse'
        '400':
          description: Missing or malformed slug, subject_id, or version.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '401':
          description: Missing or invalid Bearer token.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '404':
          description: 'Nothing pre-rendered. `slug_populated` in the body distinguishes an unbuilt slug (false) from an unknown subject on a populated slug (true).

            '
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
  /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.
      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
components:
  schemas:
    ArtifactCapabilityResponse:
      type: object
      required:
      - derived_from
      - subject_kinds
      - params
      - pairs
      - unavailable
      - counts
      properties:
        derived_from:
          type: string
          description: The source constant the response is computed from.
          example: lib/artifacts/render-client.ts::ARTIFACT_RENDER_CAPABILITY
        discovery:
          type: object
          description: Routes to call before forming a render request.
          properties:
            capability:
              type: string
              example: GET /api/artifacts/capability?subject_kind=&slug=
            as_of:
              type: string
              example: GET /api/artifacts/as-of?slug=&subject_id=&version=
        subject_kinds:
          type: array
          items:
            type: string
          description: Every kind render-svc resolves from a subject_id prefix.
        prerendered_subject_kinds:
          type: array
          items:
            type: string
          description: 'Kinds render-svc can only read from the store. See each pair''s `as_of_latest` for what `latest` does; `GET /api/artifacts/as-of` lists the dates that exist.

            '
        store_resolved_latest_kinds:
          type: array
          items:
            type: string
          description: 'Pre-rendered kinds whose `as_of=latest` resolves to the newest stored vintage (cohorts).

            '
        unimplemented_subject_kinds:
          type: object
          additionalProperties:
            type: string
          description: 'Kinds resolvable from an id prefix that no loader serves, keyed to the reason. Present so a zero-capability kind reads as measured rather than unaudited.

            '
        params:
          type: object
          properties:
            vocabulary:
              type: array
              items:
                type: string
              description: Every key the render request schema accepts.
            by_slug:
              type: object
              additionalProperties:
                type: array
                items:
                  type: string
              description: 'Applicability per slug. A slug absent from this map accepts no params. Applicability is not the accepted value set — the artifact module narrows further and answers 422 on a value it does not take.

                '
        pairs:
          type: array
          description: Verified pairs only. Nothing else may appear here.
          items:
            type: object
            required:
            - slug
            - version
            - subject_kind
            - params
            - prerendered
            properties:
              slug:
                type: string
              version:
                type: string
              subject_kind:
                type: string
              params:
                type: array
                items:
                  type: string
              prerendered:
                type: boolean
              as_of_latest:
                type: string
                enum:
                - loader
                - newest_prerendered
                - unsupported
                description: 'How `as_of=latest` resolves for this pair. `newest_prerendered` reads the newest date from `GET /api/artifacts/as-of`; `unsupported` means pass one of those dates explicitly.

                  '
              notes:
                type: string
        unavailable:
          type: array
          description: 'Pairs measured against prod and refused, with the reason. Recorded rather than deleted so a later audit does not re-add them hopefully.

            '
          items:
            type: object
            required:
            - slug
            - subject_kind
            - status
            - reason
            properties:
              slug:
                type: string
              subject_kind:
                type: string
              status:
                type: string
                enum:
                - degraded
                - unavailable
              reason:
                type: string
        counts:
          type: object
          properties:
            verified:
              type: integer
            unavailable:
              type: integer
            slugs:
              type: integer
    ArtifactAsOfResponse:
      type: object
      required:
      - slug
      - version
      - subject_id
      - subject_kind
      - as_of_latest
      - latest
      - as_of
      - vintages
      - count
      properties:
        slug:
          type: string
          example: risk_dna_stacked
        version:
          type: string
          example: v1
        subject_id:
          type: string
          description: The id as requested.
          example: BW-COHORT-RES-MAG7
        subject_kind:
          type: string
          example: cohort
        canonical_subject_id:
          type: string
          description: The canonical spelling (differs from `subject_id` only for filers).
        subject_id_spellings_searched:
          type: array
          items:
            type: string
        as_of_latest:
          type: string
          enum:
          - loader
          - newest_prerendered
          - unsupported
          description: 'What `as_of=latest` does on the render call for this subject kind: `newest_prerendered` (cohorts — the newest date below), `unsupported` (filers — pass a date from `as_of`), or `loader` (live data; this listing is informational).

            '
        latest:
          type: string
          format: date
          description: Newest pre-rendered date.
          example: '2026-04-21'
        as_of:
          type: array
          description: Date-only view of `vintages`, oldest first.
          items:
            type: string
            format: date
        vintages:
          type: array
          items:
            type: object
            required:
            - as_of
            - formats
            - params_variants
            - gcs_path
            properties:
              as_of:
                type: string
                format: date
              formats:
                type: array
                description: Formats stored for the default render (no params).
                items:
                  type: string
                example:
                - json
                - png
              params_variants:
                type: array
                description: Params fragments stored alongside, e.g. `peer_n-5`.
                items:
                  type: string
              gcs_path:
                type: object
                description: Object URI per default-render format.
                additionalProperties:
                  type: string
        count:
          type: integer
    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
    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`.

        '
      properties:
        name:
          type: string
          enum:
          - riskmodels_analyze_portfolio
          - riskmodels_compare
          - riskmodels_decompose
          - riskmodels_decompose_selection_vs_drift
          - riskmodels_get_benchmark_fit
          - riskmodels_get_cohorts
          - riskmodels_get_etf
          - riskmodels_get_factor_returns
          - riskmodels_get_filer
          - riskmodels_get_fund
          - riskmodels_get_fundamentals
          - riskmodels_get_macro_correlation
          - riskmodels_get_my_positions
          - riskmodels_get_peers
          - riskmodels_get_portfolio_risk_snapshot
          - riskmodels_get_rankings
          - riskmodels_get_residual_returns
          - riskmodels_get_residual_signal
          - riskmodels_get_return_attribution
          - riskmodels_get_returns
          - riskmodels_get_stock_snapshot
          - riskmodels_portfolio_exposure
          - riskmodels_render_artifact
          - riskmodels_screen_rankings
          - riskmodels_search
          description: 'MCP tool names (tool set 2.0, `@riskmodels/mcp` 2.0.0). One registry (`registerRiskModelsTools`) serves all

            three surfaces: the npm stdio server registers 24 tools; the hosted endpoints `/api/mcp/sse` and

            `/api/mcp/openai` register the same 24 plus `riskmodels_render_artifact` (25). Every tool is read-only and

            calls an existing REST route, billed at that route''s price; each description states when to use the tool,

            what it returns, data freshness and price. The REST catalog, response schemas and this spec are MCP

            resources (`riskmodels:///capabilities`, `riskmodels:///schemas/{path}`, `riskmodels:///openapi`).

            Tool-to-route table and the 1.x migration table: mcp/README.md (Tool set 2.0).

            '
        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.
  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)