Signals API
Pull any strategy's monthly signal as JSON or CSV with a read-only API key. Wire BestFolio straight into QuantConnect, an Interactive Brokers bot, or a spreadsheet.
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,
"signals": [
{"signal_date": "2026-05-29", "regime": "aggressive",
"allocations": {"IWM": 0.25, "QQQ": 0.25, "PDBC": 0.25, "VEA": 0.25}},
{"signal_date": "2026-06-30", "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 month-end, and at any time BestFolio publishes two recent signals: the one dated in the last completed month is the allocation to hold now (active), and the one dated in the current month is a preview for next month that only firms up once the month closes.
So do not blindly take the most recent row (limit=1), which is usually the preview. Pull a few rows and act on the latest signal whose date is in a month that has already ended. Example: on 15 June the active signal is dated 2026-05-29, and the 2026-06-30 row is July's preview.
Finding a variant_id
Each strategy has one or more variants, each with a numeric variant_id. List them from the strategies summary (GET /api/strategies/summary), find the strategy by slug, and read its variants. 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, datetime as dt
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,
)
signals = r.json()["signals"] # oldest first
def is_completed_month(date_str):
today = dt.date.today()
sd = dt.date.fromisoformat(date_str)
return (sd.year, sd.month) < (today.year, today.month)
# The latest signal in an already-closed month is the active allocation.
active = [s for s in signals if is_completed_month(s["signal_date"])][-1]
print(active["signal_date"], active["allocations"])Errors
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.