riskmodels-app Plaid Integration API

Live portfolio synchronization via Plaid Investments API.

Operations 3

POST /plaid/link-token Create Plaid Link token (Investments) #
POST /plaid/exchange-public-token Exchange Plaid public token for stored item #
GET /plaid/holdings Fetch Plaid-synced portfolio holdings #

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-plaid-integration-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-plaid-integration-api-openapi.yml Raw ↑
openapi: 3.2.0
info:
  title: RiskModels Plaid Integration 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: Plaid Integration
  description: Live portfolio synchronization via Plaid Investments API.
paths:
  /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.**
      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: []
      - OAuth2AuthorizationCode:
        - mcp:read
      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.**
      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: []
      - OAuth2AuthorizationCode:
        - mcp:read
      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.


        **Features:**

        - Automatic ticker resolution and normalization

        - Risk enrichment with factor exposures

        - Real-time portfolio valuation

        - Multi-account aggregation


        **Authentication:** Required (API Key or OAuth2 with plaid:holdings scope)

        **Billing:** $0.10 per request

        **Rate Limit:** 60 requests/minute'
      operationId: getPlaidHoldings
      tags:
      - Plaid Integration
      x-pricing:
        capability_id: plaid-holdings
        tier: premium
        model: per_request
        cost_usd: 0.1
        currency: USD
        billing_code: plaid_holdings_v3
      security:
      - BearerAuth: []
      - OAuth2AuthorizationCode:
        - mcp:read
      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.
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
    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
    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).

            '
        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.
    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
          - 'null'
        volatility:
          type:
          - number
          - 'null'
        sharpe_ratio:
          type:
          - number
          - 'null'
        l3_mkt_hr:
          type:
          - number
          - 'null'
        l3_sec_hr:
          type:
          - number
          - 'null'
        l3_sub_hr:
          type:
          - number
          - 'null'
        l3_res_er:
          type:
          - number
          - 'null'
        l3_market_hr:
          type:
          - number
          - 'null'
        l3_sector_hr:
          type:
          - number
          - 'null'
        l3_subsector_hr:
          type:
          - number
          - 'null'
        l3_residual_er:
          type:
          - number
          - 'null'
  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)