Logo

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/signalsLive 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|probeginner → 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/streamServer-Sent Events of ledger state changes. Query: cursor, family, ticker, min_score, beginner_ok.
POST/api/mcpMCP 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/size

Errors 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. Pass null for 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.