REST API · signed webhooks · MCP server — built for scripts, agents and LLMs.
Available on the Premium plan ($149/mo) · 60 requests/min per key · all results are simulations for decision support, not financial advice.
Sign in → Settings → API Access → Create key. Keys look like aim_live_…, are shown once, and can be scoped and revoked anytime. Scope keys to the minimum you need:
| Scope | Unlocks |
|---|---|
backtest:run | POST /v1/backtest — run AIM simulations |
regime:read | GET /v1/regime/{ticker} — volatility-regime read + pattern matches |
presets:read | GET /v1/presets — strategy preset catalog |
screener:read | GET /v1/screener — structural screener board (ranked on structure, never returns) |
positions:read | GET /v1/positions — your live tracked positions (sensitive — see MCP note below) |
GET /v1/me works with any valid key (no scope) and returns key info + your rate limit.
Base URL https://aimvolatilitylab.com/api/v1 — authenticate every call with Authorization: Bearer <your key>. Put the key in an env var (keys pasted inline end up in shell history): export AIM_KEY=aim_live_...
# Run a backtest (real daily bars, metrics vs buy & hold + plain-language summary)
curl -X POST https://aimvolatilitylab.com/api/v1/backtest \
-H "Authorization: Bearer $AIM_KEY" \
-H "Content-Type: application/json" \
-d '{"ticker":"AAPL","preset":"moderate","days":504}'Response (truncated). Every payload is self-describing — hypothetical, oos_validated, data provenance and a disclaimer ride along so any downstream summary (including an LLM's) inherits the caveats:
{
"ticker": "AAPL",
"bars": 504,
"params": {"equity_pct": 0.5, "safe_pct": 0.1, "atr_k": 1.5, ...},
"metrics": {
"total_return": 21.4, "buy_hold_return": 18.2, "vs_buy_hold": 3.2,
"cagr": 9.7, "sharpe": 1.19, "sortino": 1.14, "max_drawdown": -9.7,
"total_trades": 5, "total_transaction_costs": 44.95, "xirr": 9.7,
... 40+ more (taxes, turnover, friction drag, wash sales)
},
"summary": "AIM beat buy & hold by 3.2 points on AAPL over 504 trading days (...). Simulated results — not financial advice.",
"hypothetical": true,
"oos_validated": false,
"data": {"source": "yfinance daily bars", "first_bar": "2024-06-24",
"last_bar": "2026-06-22", "fetched_at": "2026-06-22T14:03:11Z"},
"disclaimer": "Historical simulation for decision support only — hypothetical results, not financial advice, no prediction of future performance."
}Backtest parameters:
| Field | Type | Meaning |
|---|---|---|
ticker | string, required | Any symbol yfinance resolves (AAPL, SPY, BTC-USD, ^GSPC). Invalid → 400. |
days | int, default 504 | Trading days of history. Clamped to 120–2000 (504 ≈ 2 years). |
preset | string, optional | One of conservative · moderate · aggressive · trend_friendly · aim_income (+ evolved presets from GET /v1/presets). Unknown → 400. |
params | object, optional | Overrides individual preset fields. Omit both preset and params → moderate. |
params.equity_pct | float 0–1 | Initial equity allocation (rest starts as cash reserve). |
params.safe_pct | float 0–1 | SAFE band — minimum move before AIM issues an order. |
params.min_order_pct | float 0–1 | Minimum order size as a fraction of portfolio control. |
params.atr_k | float | ATR multiplier for the volatility-scaled band (Enhanced engine). |
params.atr_period | int | ATR lookback in trading days (default 14). |
params.cash_ceiling_pct | float 0–1 | Max fraction of portfolio parked in cash. |
params.assessment_freq | string | weekly or monthly — how often AIM re-assesses. |
params.target_vol | float | Daily vol target for volatility scaling (e.g. 0.02). |
# Current volatility-regime read + nearest historical pattern matches curl https://aimvolatilitylab.com/api/v1/regime/SPY -H "Authorization: Bearer $AIM_KEY" # Strategy preset catalog / your live positions / key info curl https://aimvolatilitylab.com/api/v1/presets -H "Authorization: Bearer $AIM_KEY" curl https://aimvolatilitylab.com/api/v1/positions -H "Authorization: Bearer $AIM_KEY" curl https://aimvolatilitylab.com/api/v1/me -H "Authorization: Bearer $AIM_KEY" # Structural screener board — tickers ranked on STRUCTURE (choppy / mean-reverting / # non-trending), never on past returns. Disclaimer & grading status ride in the payload. curl "https://aimvolatilitylab.com/api/v1/screener?limit=25" -H "Authorization: Bearer $AIM_KEY"
All errors are JSON with a single detail string — no custom error codes to parse:
{"detail": "API rate limit exceeded (60 requests/minute)."}| Status | When | What to do |
|---|---|---|
| 401 | Missing / invalid / revoked key | Check the Bearer header; create a key in Settings → API Access. |
| 402 | Key owner is not on Premium | API access requires the Premium plan ($149/mo). |
| 403 | Key is missing the required scope | detail names the missing scope; re-issue the key with it. |
| 400 | Bad input (unknown ticker / preset, bad params) | detail says exactly what's wrong — fix and retry. |
| 429 | Over 60 requests in the current minute | Fixed 1-minute window, per key. No X-RateLimit headers — on 429, wait for the next minute boundary and retry. Don't hammer. |
A remote MCP server (Streamable HTTP) exposes the same capabilities as tools: run_backtest, get_regime_read, list_presets, list_positions. Add it as a custom connector:
Honest note: connecting an agent shares whatever the key's scopes allow with that AI provider — a key with positions:read sends your live portfolio through Claude/ChatGPT. Scope connector keys to the minimum you need.
URL: https://aimvolatilitylab.com/api/mcp
Header: Authorization: Bearer aim_live_...
# Claude: Settings → Connectors → Add custom connector → paste URL + header
# Agents (Python, official mcp SDK):
from mcp.client.streamable_http import streamablehttp_client
from mcp import ClientSession
async with streamablehttp_client("https://aimvolatilitylab.com/api/mcp",
headers={"Authorization": "Bearer aim_live_..."}) as (r, w, _):
async with ClientSession(r, w) as s:
await s.initialize()
res = await s.call_tool("get_regime_read", {"ticker": "SPY"})The safe pattern: your agent interprets verified numbers from AIM Lab — it doesn't invent them. Once connected, try:
"Backtest the moderate preset on AAPL, MSFT and NVDA over 2 years and rank them by Sharpe. Flag anything where max drawdown was worse than buy & hold's." "Before I add to my LINK position, get the current regime read for LINK-USD and tell me — plainly — whether the moderate preset historically held up in this kind of regime." "List my live positions, get the regime read for each, and summarize which ones are sitting in an elevated-volatility regime right now. Include each payload's disclaimer in your summary."
Register endpoints in Settings → Outbound Webhooks for regime.flipped, spike.detected and subscription.updated. Regime and spike payloads carry a top-level disclaimer and data_source — the caveats travel with the data, so pass them through to whatever consumes the event. Deliveries are HMAC-signed and retried (0s/30s/120s); verify like this:
# X-AIM-Signature: t=<unix_ts>,v1=<hex hmac>
import hmac, hashlib
def verify(secret: str, header: str, raw_body: bytes) -> bool:
parts = dict(p.split("=", 1) for p in header.split(","))
expected = hmac.new(secret.encode(),
f"{parts['t']}.{raw_body.decode()}".encode(),
hashlib.sha256).hexdigest()
return hmac.compare_digest(expected, parts["v1"])Rate limit 60 req/min per key · max 5 keys and 5 webhook endpoints per account · Premium tier is re-checked on every call. Questions or higher limits: use the in-app Feedback button.