riskmodels-app Authentication API

API key provisioning and OAuth2 token management.

Operations 6

POST /oauth/register Register an OAuth client (Dynamic Client Registration) #
POST /oauth/token Exchange an authorization code or refresh token for an access token #
POST /oauth/revoke Revoke an access or refresh token #
POST /auth/provision Provision an API key #
POST /auth/provision-free Provision a free-tier API key #
GET /auth/free-tier-status Free-tier usage status #

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-authentication-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-authentication-api-openapi.yml Raw ↑
openapi: 3.2.0
info:
  title: RiskModels Authentication 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: Authentication
  description: API key provisioning and OAuth2 token management.
paths:
  /oauth/register:
    post:
      summary: Register an OAuth client (Dynamic Client Registration)
      description: 'RFC 7591 dynamic client registration. MCP clients self-register here after discovering the authorization server at `/.well-known/oauth-authorization-server`.


        Public clients only: no `client_secret` is issued and `token_endpoint_auth_method` is `none` — the authorization code grant is bound to the client by PKCE instead. `redirect_uris` must be absolute; `http://` is accepted only for loopback hosts (RFC 8252).


        Rate limited to 30 registrations per IP per hour.'
      operationId: registerOAuthClient
      tags:
      - Authentication
      security: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/OAuthClientRegistrationRequest'
            example:
              client_name: Claude Desktop
              redirect_uris:
              - https://claude.ai/api/mcp/auth_callback
      responses:
        '201':
          description: Client registered
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/OAuthClientRegistrationResponse'
        '400':
          description: '`invalid_client_metadata` (body is not JSON) or `invalid_redirect_uri` (`redirect_uris` missing, empty, non-absolute, or a non-loopback `http://` URI).

            '
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/OAuth2Error'
        '429':
          description: Registration rate limit exceeded
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/OAuth2Error'
        '500':
          description: server_error — client record could not be persisted
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/OAuth2Error'
  /oauth/token:
    post:
      summary: Exchange an authorization code or refresh token for an access token
      description: 'OAuth 2.0 token endpoint. Supports exactly two grants — `authorization_code` (PKCE S256, single-use codes) and `refresh_token` (rotating). There is **no** `client_credentials` grant; presenting one returns `unsupported_grant_type`.


        The issued `access_token` is an `rm_user_*` API key valid for 1 hour, usable as `Authorization: Bearer …` against any endpoint in this spec. The `refresh_token` is valid for 30 days and rotates on every use — replaying an already-rotated refresh token revokes the whole token family for that user/client pair (RFC 6819 §5.2.2.3).


        Accepts either `application/x-www-form-urlencoded` or `application/json`. Responses are `Cache-Control: no-store`. Rate limited to 60 requests per IP per minute.'
      operationId: exchangeOAuthToken
      tags:
      - Authentication
      security: []
      requestBody:
        required: true
        content:
          application/x-www-form-urlencoded:
            schema:
              $ref: '#/components/schemas/OAuth2TokenRequest'
          application/json:
            schema:
              $ref: '#/components/schemas/OAuth2TokenRequest'
            example:
              grant_type: authorization_code
              code: 9f2c1d…
              redirect_uri: https://claude.ai/api/mcp/auth_callback
              client_id: 3f8a1c2e-5b47-4d90-9e21-7c6b0a4d8f13
              code_verifier: dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk
      responses:
        '200':
          description: Access token issued
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/OAuth2TokenResponse'
              example:
                access_token: rm_user_live_abc123_xyz789
                token_type: Bearer
                expires_in: 3600
                refresh_token: 5f3a…
                scope: mcp:read
        '400':
          description: '`invalid_request` (missing parameters), `invalid_grant` (code/refresh token not found, expired, already used, or PKCE/client mismatch), or `unsupported_grant_type`.

            '
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/OAuth2Error'
        '429':
          description: Token rate limit exceeded
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/OAuth2Error'
        '500':
          description: server_error — token issuance failed
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/OAuth2Error'
  /oauth/revoke:
    post:
      summary: Revoke an access or refresh token
      description: RFC 7009 token revocation. Revokes the presented `rm_user_*` access token or refresh token. Rate limited to 60 requests per IP per minute. Per RFC 7009 the endpoint returns 200 even for unknown tokens.
      operationId: revokeOAuthToken
      tags:
      - Authentication
      security: []
      requestBody:
        required: true
        content:
          application/x-www-form-urlencoded:
            schema:
              $ref: '#/components/schemas/OAuth2RevokeRequest'
          application/json:
            schema:
              $ref: '#/components/schemas/OAuth2RevokeRequest'
      responses:
        '200':
          description: Token revoked (or was already invalid). Body is an empty object.
        '400':
          description: invalid_request — `token` parameter missing
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/OAuth2Error'
        '429':
          description: Revocation rate limit exceeded
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/OAuth2Error'
  /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.
      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.
      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.
      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'
components:
  schemas:
    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
    OAuth2Error:
      type: object
      properties:
        error:
          type: string
          enum:
          - invalid_request
          - invalid_client_metadata
          - invalid_redirect_uri
          - invalid_grant
          - unsupported_grant_type
          - too_many_requests
          - server_error
        error_description:
          type: string
    OAuth2TokenResponse:
      type: object
      properties:
        access_token:
          type: string
          example: rm_user_live_abc123_xyz789
          description: 'An `rm_user_*` API key. Use directly as `Authorization: Bearer …` against any endpoint in this spec.

            '
        token_type:
          type: string
          enum:
          - Bearer
        expires_in:
          type: integer
          example: 3600
          description: Access token lifetime in seconds (3600 = 1 hour).
        refresh_token:
          type: string
          description: 'Valid 30 days and rotated on every use. Replaying a rotated token revokes the whole family for that user/client pair.

            '
        scope:
          type: string
          example: mcp:read
    OAuthClientRegistrationResponse:
      type: object
      properties:
        client_id:
          type: string
          format: uuid
          example: 3f8a1c2e-5b47-4d90-9e21-7c6b0a4d8f13
        client_id_issued_at:
          type: integer
          description: Unix timestamp (seconds).
        redirect_uris:
          type: array
          items:
            type: string
            format: uri
        grant_types:
          type: array
          items:
            type: string
          example:
          - authorization_code
          - refresh_token
        response_types:
          type: array
          items:
            type: string
          example:
          - code
        token_endpoint_auth_method:
          type: string
          enum:
          - none
          description: Public client — no client_secret is issued; PKCE binds the grant.
        client_name:
          type: string
          description: Present only if supplied at registration.
    OAuthClientRegistrationRequest:
      type: object
      required:
      - redirect_uris
      properties:
        redirect_uris:
          type: array
          minItems: 1
          items:
            type: string
            format: uri
          description: 'Absolute redirect URIs. `https://` and custom (native app) schemes are accepted; `http://` only for loopback hosts (`localhost`, `127.0.0.1`, `::1`).

            '
          example:
          - https://claude.ai/api/mcp/auth_callback
        client_name:
          type: string
          maxLength: 200
          example: Claude Desktop
      additionalProperties: true
    OAuth2TokenRequest:
      type: object
      required:
      - grant_type
      description: 'Shape depends on `grant_type`. For `authorization_code`: `code`, `redirect_uri`, `client_id`, `code_verifier` are all required. For `refresh_token`: `refresh_token` and `client_id` are required.

        '
      properties:
        grant_type:
          type: string
          enum:
          - authorization_code
          - refresh_token
          description: '`client_credentials` is not supported and returns `unsupported_grant_type`.

            '
        code:
          type: string
          description: Single-use authorization code (authorization_code grant).
        redirect_uri:
          type: string
          format: uri
          description: Must match the URI the code was issued for (authorization_code grant).
        client_id:
          type: string
          format: uuid
          description: Client id from `POST /api/oauth/register`.
        code_verifier:
          type: string
          description: PKCE verifier; must hash (S256) to the registered code_challenge.
        refresh_token:
          type: string
          description: Rotating refresh token (refresh_token grant).
    OAuth2RevokeRequest:
      type: object
      required:
      - token
      properties:
        token:
          type: string
          description: 'The `rm_user_*` access token or the opaque refresh token to revoke. The endpoint infers which from the `rm_user_` prefix.

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