Authentication Guide
The RiskModels API supports four authentication modes (as of v3.0.0-agent). Choose based on your application type.
Mode 1 — Bearer Token (Direct API Key)
All external API calls use a Bearer token in the Authorization header.
Authorization: Bearer rm_agent_live_<random>_<checksum>
Token format: rm_agent_{environment}_{random}_{checksum} or rm_user_{random}_{checksum}
environment:live(production) ortest(sandbox)- Tokens are long-lived but can be rotated from the dashboard
Understanding rm_agent vs live (same key, two labels)
Keys from Get your API key look like rm_agent_live_…. That is one prefix, not two different products:
| Part | Meaning |
|---|---|
rm_agent_ | The key lives in the metered / prepaid API program (agent_api_keys). Use it for REST, Python SDK, Node CLI, MCP, notebooks, and automation. The word agent refers to this billing program, not “only for LLM agents.” |
live | Environment: production API and live datasets at riskmodels.app. (A test segment would indicate a sandbox key when issued.) |
rm_user_… keys use a different issuance path (user_generated_api_keys). If you only created keys on the Get Key page, you have rm_agent_… keys.
Obtaining a Token
Option A — Dashboard:
- Get your API key (sign up if you haven't already)
- Generate a new key and copy the token
- Store securely in your environment variables
Option B — API provisioning endpoint (for AI agents):
curl -X POST https://riskmodels.app/api/auth/provision \
-H "Authorization: Bearer <session-jwt>" \
-H "Content-Type: application/json"
Plaid and Account Identity
If you plan to use GET /api/plaid/holdings, the Plaid connection and the API key must belong to the same user account.
- For dashboard signup and
POST /api/auth/provision, that account is identified by email - Sign in at riskmodels.net with that same email to connect Plaid in the web app
POST /api/auth/provision-freecreates an anonymous API account with no web login, so Plaid holdings are not available for that account
Billing
Tokens use a prepaid balance model:
- Add credit via Stripe (scroll to "Add Credit" section)
- Each metered request deducts from your balance
- Check balance:
GET /api/balance - Cached responses are free (
cost_usd: 0in the_agentblock) - Minimum top-up: $10.00 USD
Mode 2 — OAuth 2.0 Authorization Code + PKCE (MCP clients)
client_credentialsis not supported. Earlier versions of this page documented a machine-to-machineclient_credentialsgrant againstPOST /api/auth/token. That endpoint was never implemented and returns 404, and/api/oauth/tokenrejects the grant withunsupported_grant_type. For servers, agents, and CLI tools, use Mode 1 (Bearer API key) — there is no token-exchange step.
This mode exists for MCP clients — Claude Desktop, Cursor, ChatGPT Developer Mode, Grok — which self-register and sign in interactively. Most of them drive the whole flow for you: paste the MCP URL, click through the OAuth sign-in, leave client id/secret blank.
The authorization server describes itself at
/.well-known/oauth-authorization-server,
which is the source of truth for this flow.
| Property | Value |
|---|---|
| Grants | authorization_code, refresh_token |
| Authorization endpoint | https://riskmodels.app/oauth/authorize |
| Token endpoint | https://riskmodels.app/api/oauth/token |
| Registration endpoint | https://riskmodels.app/api/oauth/register |
| Revocation endpoint | https://riskmodels.app/api/oauth/revoke |
| PKCE | Required, S256 |
| Client authentication | none — public clients, no client_secret is issued |
| Scopes | mcp:read |
| Access token lifetime | 1 hour |
| Refresh token lifetime | 30 days, rotating |
Building a client by hand
1. Register (RFC 7591)
curl -X POST https://riskmodels.app/api/oauth/register \
-H "Content-Type: application/json" \
-d '{
"client_name": "My MCP Client",
"redirect_uris": ["https://example.com/callback"]
}'
Returns a client_id. redirect_uris must be absolute; http:// is accepted only for
loopback hosts (RFC 8252). Limited to 30 registrations per IP per hour.
2. Authorize
Send the user to https://riskmodels.app/oauth/authorize with response_type=code, your
client_id, redirect_uri, scope=mcp:read, state, and a PKCE code_challenge
(code_challenge_method=S256). On approval you receive a single-use code.
3. Exchange the code
curl -X POST https://riskmodels.app/api/oauth/token \
-H "Content-Type: application/x-www-form-urlencoded" \
-d "grant_type=authorization_code" \
-d "code=$CODE" \
-d "redirect_uri=https://example.com/callback" \
-d "client_id=$CLIENT_ID" \
-d "code_verifier=$CODE_VERIFIER"
{
"access_token": "rm_user_live_abc123_xyz789",
"token_type": "Bearer",
"expires_in": 3600,
"refresh_token": "5f3a...",
"scope": "mcp:read"
}
The access_token is an rm_user_* API key — use it exactly like a Mode 1 key.
4. Refresh
curl -X POST https://riskmodels.app/api/oauth/token \
-H "Content-Type: application/x-www-form-urlencoded" \
-d "grant_type=refresh_token" \
-d "refresh_token=$REFRESH_TOKEN" \
-d "client_id=$CLIENT_ID"
Refresh tokens rotate: each use returns a new one and invalidates the old. Replaying an already-rotated token is treated as a theft signal and revokes every token for that user/client pair, so persist the new value on every refresh.
A note on scopes
mcp:read is the only scope the authorization server advertises. Scope is currently
informational — the API records it for telemetry but authorises on key validity and
account balance, not scope. Do not rely on it for access control.
Mode 3 — Supabase JWT (Browser / Mobile Apps)
For applications that directly query Supabase (the underlying database), use the public anon key with user authentication.
import { createClient } from '@supabase/supabase-js';
const supabase = createClient(
'https://your-project.supabase.co',
'your-anon-key' // Safe to expose in client-side code
);
// Sign in (passwordless magic link)
await supabase.auth.signInWithOtp({ email: 'user@example.com' });
// After sign-in, JWT is automatically attached to queries
const { data } = await supabase
.from('security_history_latest')
.select('symbol, returns_gross, vol_23d, l3_mkt_hr, l3_sec_hr, l3_sub_hr')
.eq('symbol', 'BW-US67066G1040')
.eq('periodicity', 'daily');
Row Level Security (RLS) is enforced — users can only access data they are authorised for.
AI Agent Provisioning Flow
Recommended pattern for LLM agents integrating with the RiskModels API:
-
Discover capabilities
GET /.well-known/agent-manifestReturns service metadata, all endpoint capabilities, pricing, and the provisioning URL.
-
Provision a token
POST /api/auth/provisionExchange a session JWT for a long-lived Bearer API key.
-
Check balance before starting a workflow
GET /api/balanceVerify
status.can_make_requestsistrueandbalance_usdis sufficient. -
Make data requests
Authorization: Bearer rm_agent_live_... -
Monitor cost per request Read
_agent.cost_usdin each response body, or theX-API-Cost-USDheader.
Rate Limits
Per-API-Key Rate Limiting: All authenticated endpoints are rate limited on a per-API-key basis using a sliding window algorithm.
| Tier | Requests / Minute | Daily Limit | Burst |
|---|---|---|---|
| Default (pay-as-you-go) | 60 | Unlimited | 100 |
Premium (rate:300 scope) | 300 | Unlimited | 500 |
| Max concurrent | 10 | — | — |
Rate Limit Headers
All responses include rate limit information:
X-RateLimit-Limit: 60
X-RateLimit-Remaining: 42
X-RateLimit-Reset: 1709856000
429 Too Many Requests
When rate limit is exceeded, you'll receive:
HTTP/1.1 429 Too Many Requests
Retry-After: 23
Best Practice: Implement exponential backoff starting at the Retry-After value.
Security Notes
- Never commit API keys to source control
- Use environment variables:
RISKMODELS_API_KEY=rm_agent_live_... - Rotate keys from the dashboard if compromised
- Service role key must never appear in browser-side code
- Test keys (
rm_agent_test_...) return simulated responses and do not deduct balance
Related
Sign up, generate your token, and add credit to get started immediately.
🏦 Plaid HoldingsSee the one-time web flow for connecting a brokerage and reusing that account through the API.
🤖 Agent IntegrationConfigure your key in Cursor, Claude Desktop, Zed, or the npm CLI.
📄 API DocsAll endpoints, costs, and response field definitions.