API changelog
Changes that affect code you have written against the API. For the full engineering history
see CHANGELOG.md
in the repository.
October 2026 — exposure history feeds follow the served-history start
POST /api/portfolio/exposure/history and /history/daily now serve model history from the same
start dates as every other model-history route: hedge ratios and l1_mkt_beta from 2010-12-08,
explained-risk and residual shares and stock_var from 2011-12-07, lstar_level from 2013-01-01.
Earlier values are no longer delivered (they were estimated on a partial window). The default
start is 2010-12-08 and an earlier start is moved up to it. Responses carry
history_available_from (first date per column) and warnings (before_served_history) when
start is earlier than a column's first date.
October 2026 — long/short portfolio exposure
New: POST /api/portfolio/exposure. Send a signed book of dollar positions (shorts
negative, up to 1000 names, ETFs allowed). Nothing is normalised. The response carries L1
beta-dollars by sector, ETF hedge trades at L*, L1, L2 and L3 (stock hedge, ETFs already held,
and the net neutralising trade), a systematic and residual risk split, coverage of the submitted
gross by each calculation, and a warnings array. as_of computes the same output at a past
model date. 0.25 per call up to 25 names modelled, 1.00 above.
New: exposure history feeds. POST /api/portfolio/exposure/history (month-end) and
POST /api/portfolio/exposure/history/daily take tickers only and return Parquet files with each
name's model history and the ETF covariance at each month-end. Holdings stay on the
client; client.exposure_history(...) in riskmodels-py joins them locally. 1.25 / 5.00
(month-end) and 2.50 / 10.00 (daily), split at 25 names delivered.
Residual shares above 1. Explained-risk shares at a level sum to 1, but an individual share
can be negative when a hedge leg added variance over the window, and the residual share can then
exceed 1. /portfolio/exposure uses such a share as is and reports a hedge_added_variance
warning. Guide: Long/short exposure.
August 2026 — catalogue reprice
Self-serve rates moved on 14 August 2026. Lookups are 0.02 + 0.015/position (25% off GET /lstar).
Institutional licenses: Desk 150k / Production $250k per year.
Keys that recorded a paid call before that date keep prior rates through 31 December 2026.
Current schedule: Pricing.
July 2026 — contract corrections, client releases
Python SDK riskmodels-py 0.4.0 · npm CLI riskmodels 3.0.0
pip install -U riskmodels-py
npm install -g riskmodels
Breaking: OAuth client credentials removed from both clients
If you authenticate with an API key, nothing changes and no action is needed.
If you configured client_id / client_secret — or the RISKMODELS_CLIENT_ID,
RISKMODELS_CLIENT_SECRET, RISKMODELS_OAUTH_SCOPE environment variables — that path has
been removed from both clients.
# Before
client = RiskModelsClient(client_id="...", client_secret="...")
# After
client = RiskModelsClient(api_key="rm_agent_live_...")
For the CLI, use RISKMODELS_API_KEY or riskmodels config set apiKey .... The
clientId / clientSecret / oauthScope config keys are gone.
Why: those clients posted a client_credentials grant to POST /api/auth/token, an
endpoint that was documented but never implemented — it returns 404, so that path failed on
its first request for every user who tried it. Supplying those credentials now raises
immediately with migration instructions instead of constructing a client that cannot work.
Authentication documentation corrected
The OpenAPI security scheme described a client_credentials flow the API does not implement.
It now describes the flow that exists — authorization code + PKCE, used by MCP clients —
copied from /.well-known/oauth-authorization-server.
POST /api/auth/token— never existed, removed from the specPOST /api/oauth/token— the real token endpoint (authorization_code,refresh_token)POST /api/oauth/register,POST /api/oauth/revoke— now documented
See Authentication for the current flow.
Nine endpoints added to the OpenAPI spec
These were always callable but were missing from the published spec, so they could not be discovered from the contract:
| Endpoint | Description | Cost |
|---|---|---|
POST /v4/decompose | Named-block decomposition (market / industry / style / stock-specific) | $0.005 |
GET /hedge-basket/{ticker} | Hedge basket with recommended level and decision trace | $0.02 |
GET /batch/latest-metrics | Latest L3 scalars for up to 100 tickers | $0.005 |
GET /metrics/{ticker}/snapshot.png | Snapshot as PNG bytes | $1.25 |
GET /snapshot/{entity_kind} | Deep Dive snapshot, PNG or PDF bytes | $1.25 |
GET /funds/search | Fund discovery → bw_fund_id | free |
POST /oauth/register · /oauth/token · /oauth/revoke | MCP OAuth flow | free |
The two snapshot endpoints return image or PDF bytes, not JSON.
Response behaviour
Rate-limited JSON endpoints now return 429. Throttled public endpoints previously
returned HTTP 200 with a status-badge body, which parses as a successful empty result. If
your code treats an empty response as "no more data", add a 429 check. Badge endpoints
still return 200 when throttled, because image badges require it.
Public search endpoints are rate limited. GET /api/funds/search and
GET /api/13f/filers/search still require no API key, but now allow 60 requests/minute per
IP and return at most 100 rows per call.
500 responses no longer include internal exception text. You get a generic message; the
detail is in server-side logs. Include the request_id from the response when reporting an
issue.
402 responses point to a working URL. The top_up_url field and X-Top-Up-URL header
previously returned /api/billing/top-up, which does not exist. They now return
riskmodels.app/get-key. There is no programmatic top-up
endpoint — top-ups go through Stripe Checkout.
Removed
GET /api/pdf/{symbol}/latest — undocumented and unused. Use
GET /api/metrics/{ticker}/snapshot.pdf.
Also
llms.txtpointed at/openapi(404); it now points at/openapi.json.- The Python wheel dropped from 33 MB to 0.33 MB — it was bundling render output that is not part of the package.
Reporting a discrepancy
If anything in the documentation disagrees with what the API actually returns, the API is correct and the documentation is a bug. Please report it — that mismatch is exactly what this release set out to fix.