Signals API & MCP
QuantCore publishes scored, explained signals. Your agent reads them, sizes the trade and places it with your own broker keys. Everything here is read-only: there is no endpoint or tool that places an order, and QuantCore never holds your broker keys.
Auth
Create a key at /developers (needs an active plan). Keys look like qc_live_…, are shown once, and are stored only as a hash. Send it on every request:
Authorization: Bearer qc_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
Lost a key? Rotate it on the dashboard: the old key stops working immediately. /api/v1/track-record also works without a key. All REST endpoints send CORS headers, so browser code can call them, but never ship your key in a public web page.
Rate limits
5,000 calls per key per day, reset at midnight New York time. Every response carries X-RateLimit-Limit, X-RateLimit-Remaining and X-RateLimit-Reset (seconds). Over the limit you get 429 with Retry-After. A stream connection counts as one call. On MCP only tools/call counts; initialize, tools/list and ping are free.
The 15-minute delay
Market data behind signals is delayed 15 minutes. Every signal says so in freshness_class (delayed_15m), data_as_of and delay_note. Because of the delay the engine only publishes signals with a horizon of one trading day or more; a 15-minute delay is under 4% of a 1–5 day swing. Always call check_trade with a live quote from your broker before you order.
REST v1
| GET | /api/v1/signals | Live signals. Query: family, direction, min_score, beginner_ok, ticker, limit (≤200). |
| GET | /api/v1/signals/{id} | One signal with every version so far, oldest first. |
| GET | /api/v1/signals/{id}/explain?level=beginner|pro | beginner → plain block; pro → thesis, risks, numbers, features. |
| GET | /api/v1/track-record?family=&horizon= | Hit rate, average R, vs SPY, calibration. No key needed. |
| POST | /api/v1/size | {signal_id, account_value, risk_pct, max_notional?} → qty, notional, risk_dollars, bracket. |
| POST | /api/v1/check | {signal_id, live_price, max_drift_pct|null} → ok | stale | drifted, with reason. |
| GET | /api/v1/stream | Server-Sent Events of ledger state changes. Query: cursor, family, ticker, min_score, beginner_ok. |
| POST | /api/mcp | MCP server (Streamable HTTP, JSON-RPC 2.0). |
curl -H "Authorization: Bearer $QUANTCORE_API_KEY" \
"https://qntcore.ai/api/v1/signals?beginner_ok=true&min_score=70"
curl -X POST -H "Authorization: Bearer $QUANTCORE_API_KEY" \
-H "Content-Type: application/json" \
-d '{"signal_id":"8812","account_value":1000,"risk_pct":1}' \
https://qntcore.ai/api/v1/sizeErrors are JSON: {"error": "invalid_input", "message": "…"} with status 400, 401 (missing or revoked key), 404, 429 or 500. A signal that fails QuantCore's provenance check is never returned; it looks like a 404.
risk_pct is the loss at the stop as a percent of account_value. Any positive value is accepted. The only platform limit is a $250,000 notional fat-finger cap.
check_trade: ok, stale, drifted
- stale: the signal is no longer live (invalidated, expired, scored), its horizon has passed, or the live price is already at or through the stop or the target.
- drifted: the live price is further from entry than your
max_drift_pct, in either direction. Passnullfor warn-only: never drifted, but a warning is returned above 1%. - ok: none of the above. Good for 30 minutes (
valid_until); check again if you have not sent the order by then.
Streaming (SSE)
QuantCore runs on Vercel, which cannot host raw WebSockets, so state changes stream as Server-Sent Events instead. Each ledger change is one signal event whose id is a cursor. Connections close after about 280 seconds with a reconnect event; reconnect with Last-Event-ID (or ?cursor=) and you miss nothing. Without a cursor you get changes from now on. Browser EventSource cannot send headers, so use a fetch-based SSE client from your agent.
curl -N -H "Authorization: Bearer $QUANTCORE_API_KEY" \
"https://qntcore.ai/api/v1/stream?family=flow%2Bgex"
id: 10423
event: signal
data: {"state":"strengthened","signal":{…}}
id: 10423
event: heartbeat
data: {"cursor":10423}MCP server
Endpoint https://qntcore.ai/api/mcp, Streamable HTTP transport, protocol version 2025-06-18. It is stateless: every JSON-RPC message is a POST answered with JSON (no session id, GET returns 405). Same key, same limits.
claude mcp add --transport http quantcore https://qntcore.ai/api/mcp \ --header "Authorization: Bearer $QUANTCORE_API_KEY"
| list_signals(filters) | Live signals, filterable by family, direction, min_score, beginner_ok, ticker. |
| get_signal(signal_id) | Full signal with every version so far. |
| explain_signal(signal_id, level) | Plain block (beginner) or thesis and numbers (pro). |
| get_track_record(family, horizon) | Hit rate, average R, calibration, sample size. |
| size_position(signal_id, account_value, risk_pct, max_notional?) | Quantity, dollars at risk, bracket. Any risk_pct; only the fat-finger cap applies. |
| check_trade(signal_id, live_price, max_drift_pct) | ok / stale / drifted with the reason. Drift tolerance is yours. |
| subscribe(filters) | Returns the SSE URL and a cursor for state changes. |
Deliberately missing: place_order. Orders happen in your own code with your own broker keys. Tool results come back as JSON text content plus structuredContent. Every number your agent shows a person should come from these results.
Schemas
A signal (impersonal and versioned; the same for every user):
{
"signal_id": "8812",
"version": 2,
"engine_version": "2026.10.3",
"state": "opened", // opened | strengthened | weakened | invalidated | expired | scored
"ticker": "NVDA",
"asset": "equity", // equity | option
"direction": "long", // long | short
"score": 74, // 0..100
"family": "flow+gex", // flow+gex | off-exchange | insider
"entry": 221.30, "target": 236.00, "stop": 214.50,
"reward_risk": 2.2,
"horizon_days": 5,
"data_as_of": "2026-10-15T18:17:00Z",
"freshness_class": "delayed_15m",
"delay_note": "Data as of 2026-10-15T18:17:00Z (15-minute delay).",
"sources": ["massive:options_snapshot", "massive:trades", "public:form4"],
"raw_refs": ["scan_20261015T1830_0412"],
"confidence_label": "high", // high | medium | low
"thesis": { "model": "pinned-id", "prompt_v": 3, "text": "…", "risks": "…",
"confidence_label": "high" },
"plain": {
"headline": "Big traders are betting NVDA rises this week",
"why_now": "…",
"what_breaks_it": "If NVDA falls below $214.50, the idea is wrong and the signal ends.",
"risk_in_dollars": "On a $1,000 paper account at 1% risk, the most you'd lose at the stop is about $10.00 …",
"learn": { "lesson": "What is options flow?", "url": "https://hub.qntcore.ai/…" }
},
"beginner_ok": true, // false for options, earnings within horizon, or high volatility
"features": { … },
"created_at": "2026-10-15T18:31:04Z"
}Entry, target and stop are fixed by code when a signal opens. The language model writes only text; every number in plain is copied from the fields by code.
Track record:
{
"engine_version": "2026.10.3", "family": "all", "since": "2026-10-20T…",
"scored": 142, "hit_rate": 57, "avg_r": 0.42, "vs_spy_pts": 3.1,
"calibration": [ { "label": "high", "hit_rate": 64, "n": 41 }, … ],
"horizon_day": 5,
"explained": "142 signals were scored 5 trading days after they opened. …"
}size_position / POST /api/v1/size, for account_value 10000 and risk_pct 1:
{
"qty": 14, "notional": 3098.2, "risk_dollars": 95.2, "risk_pct_of_account": 0.95,
"bracket": { "tp": 236.0, "sl": 214.5 }, "side": "buy", "reason": null,
"signal_id": "8812", "version": 2, "ticker": "NVDA", "entry": 221.3,
"signal_live": true, "fat_finger_cap": 250000
}When you place the order, use client_order_id = signal_id:your_user_id so a retry can never open the same trade twice.
Starter kit
npx quantcore-starter --paper runs a one-file agent on Alpaca paper with your own paper keys: fetch signals, keep beginner_ok, size, check against a live quote, place a bracket order, log. Guardrails ship on (1% risk, 3 open, 2% daily stop, no options) and every one of them is yours to change, including off. npx quantcore-starter report compares your paper results with the public track record.
Signals are educational data, not investment advice. Past results do not guarantee future results.