Signals API
Pull any strategy's monthly signal, plus your portfolio's allocations and backtest, as JSON or CSV with a read-only API key. Wire BestFolio into QuantConnect, an Interactive Brokers bot, or a spreadsheet, or connect your AI assistant over MCP.
What it is
A read-only HTTP API that returns a strategy variant's monthly rebalancing signal in machine-readable form, so you can feed BestFolio signals straight into whatever executes your trades. It is the same signal you see on the site and in the monthly email, just in a format your code can read.
Authentication
Every request needs a BestFolio API key. Keys start with bf_live_ and are tied to your Pro subscription (access stops if the subscription lapses). Pass it either way:
- Header:
X-API-Key: bf_live_... - Or:
Authorization: Bearer bf_live_...
Keys are read-only and should be treated like a password. Pro members generate and revoke keys in Settings, under API Keys.
Endpoint
GET /api/variants/{variant_id}/signal/exportQuery parameters:
format:jsonorcsv. CSV columns aresignal_date,regime, then one column per ETF holding the target weight as a decimal (0.25 = 25%).limit: number of most recent signals to return (1 to 1000, default 120). Returned oldest first.
Example JSON response
{
"variant_id": 1,
"count": 2,
"current_signal": "2026-05-29",
"next_signal": "2026-06-30",
"as_of_date": "2026-06-15",
"signals": [
{"signal_date": "2026-05-29", "effective_date": "2026-05-29",
"as_of_date": "2026-06-15", "status": "current", "regime": "aggressive",
"allocations": {"IWM": 0.25, "QQQ": 0.25, "PDBC": 0.25, "VEA": 0.25}},
{"signal_date": "2026-06-30", "effective_date": "2026-06-30",
"as_of_date": "2026-06-15", "status": "preview", "regime": "aggressive",
"allocations": {"IWM": 0.25, "QQQ": 0.25, "PDBC": 0.25, "VEA": 0.25}}
]
}Active signal vs next-month preview
Read this before you automate
Signals are dated at their effective date. The newest signal effective on or before the response's UTC as_of_date is current. A signal with a later effective date is a preview and is not actionable until that date.
Do not blindly take the most recent row (limit=1), which may be the preview. Example: on 15 June the current signal is effective 2026-05-29 and the 2026-06-30 row is a preview. On 30 June, that preview becomes current.
To make this explicit, the JSON response includes top-level current_signal (the active allocation) and next_signal (the preview) dates. Each JSON row also includes status, as_of_date, and effective_date. Strategy summary and detail responses use the same fields inside current latest_signal and nullable preview_signal.
Finding a variant_id
Each strategy has one or more variants, each with a numeric variant_id. The quickest way is to resolve a slug to its variants with GET /api/strategies/slug/{slug}/variants (returns id, name, slug, and active per variant), or pull the full GET /api/strategies/summary. Common HAA (Hybrid Asset Allocation, Keller) variants:
| variant_id | variant | notes |
|---|---|---|
| 1 | HAA Standard (with QQQ) | the canonical HAA |
| 2 | HAA without QQQ | |
| 3 | HAA Leveraged (2x) | |
| 4 | HAA Leveraged, no QQQ | |
| 82 | HAA SmartLeverage 1.5x | |
| 66 | HAA SmartStack (Gold + MF) | |
| 155 | NLX HAA 60/40 | |
| 185 | HAA-Simple | |
| 186 | HAA-Simple Leveraged 2x (SSO) |
Examples
JSON, recent history
curl -H "X-API-Key: bf_live_..." \
"https://bestfolio.app/api/variants/1/signal/export?format=json&limit=6"CSV, for a spreadsheet
curl -H "X-API-Key: bf_live_..." \
"https://bestfolio.app/api/variants/1/signal/export?format=csv&limit=24"Python, pick the active signal correctly
import requests
KEY = "bf_live_..."
r = requests.get(
"https://bestfolio.app/api/variants/1/signal/export",
params={"format": "json", "limit": 6},
headers={"X-API-Key": KEY},
timeout=30,
)
payload = r.json()
signals = payload["signals"] # oldest first
# BestFolio resolves the date in UTC. Use the declared current signal instead
# of choosing the maximum stored date, which may be a future preview.
active_date = payload["current_signal"]
active = next(s for s in signals if s["effective_date"] == active_date)
print(active["signal_date"], active["allocations"])More endpoints
The same key also reaches your portfolio analytics and richer strategy data. Portfolio endpoints return only portfolios you own. All are read-only GET requests using the same authentication.
A machine-readable OpenAPI spec for these endpoints lives at /api/v1/openapi.json, for generating a client or importing into your tooling.
| method | path | returns |
|---|---|---|
| GET | /api/strategies/summary | All strategies with the current latest_signal and nullable future preview_signal, in your tier's view. |
| GET | /api/strategies/slug/{slug} | Full detail for one strategy: variants, metrics, signals. |
| GET | /api/portfolios/{id}/rollup | Your portfolio's net exposure: asset-class, regional, turnover. |
| GET | /api/portfolios/{id}/rollup/execution | Execution-ready allocation after small-position merging. |
| GET | /api/portfolios/{id}/processed-allocation | Final ticker-to-weight map after UCITS and rollup. |
| GET | /api/portfolios/{id}/drift | Target vs actual drift per sleeve for your portfolio. |
| GET | /api/portfolios/{id}/trade-list | Buy/sell/hold list. Params: portfolio_size, period (current or next). |
| GET | /api/portfolios/{id}/backtest | Blended NAV, returns, metrics, drawdown. Params: start, end, currency. |
| GET | /api/portfolios/{id}/wf-nav | Cached walk-forward NAV series and metrics. |
Connect your AI assistant (MCP)
BestFolio runs a remote Model Context Protocol server, so you can ask Claude or ChatGPT about your strategies, signals, and portfolios directly. Add it as a custom connector and authenticate with your bf_live_ key.
MCP server URL
https://bestfolio.app/mcpAdd the URL above as a custom connector and sign in with your BestFolio account when prompted. There is no key to copy: the connector is authorised against your account directly, and you can disconnect it at any time. The server is read-only and Pro-gated, exactly like the HTTP API.
Claude Code and other terminal clients can skip the sign-in and present an API key instead:
Claude Code
claude mcp add --transport http bestfolio \
https://bestfolio.app/mcp \
--header "Authorization: Bearer bf_live_..."Available tools:
list_strategiesget_strategy_signalget_strategy_detailget_portfolio_rollupget_portfolio_executionget_portfolio_allocationget_portfolio_driftget_portfolio_trade_listget_portfolio_backtestget_portfolio_walkforwardErrors
Fair use
Signals update at most once per trading day, after the daily scan. Polling once a day is plenty. Please do not hammer the endpoint.