API reference
Two market-analysis endpoints, one header, no SDK. Start with a free key by email; that is the only key, and no paid plan is offered on the site.
New to the terms on this page? Learn explains day types, volume-price analysis and how to read a probability from zero, and Guides walks through max pain and gamma exposure by hand. This page assumes you just want to call things.
Base URL and authentication
Every endpoint lives under https://api.quantdata.uk, and every one of them takes a single header:
Machine-readable contract:
https://api.quantdata.uk/openapi.json. Import that OpenAPI document when an
agent platform supports Actions or tool schemas; this page remains the human-readable reference.
X-API-Key: qd_9f3c...
Keys always begin with qd_. There is no OAuth flow, no bearer token exchange and no
session cookie — the header is the entire authentication story, which is deliberate: it means
any AI agent that can make an HTTP request can use the API without an integration project.
Requests are HTTPS only. Treat a key like a password; if one leaks, email us and we will rotate
it.
Start with one anonymous look, then get a free key
Omit the header and one source can receive 1 complete first-look
response per UTC day. That first response has the same analytical fields and no watermark.
Its free_tier.next_action points to the free-key request.
To go further, request a free qd_ key with an email address. No login,
password, card or GUI is required:
curl -s -X POST https://api.quantdata.uk/v1/access/free-key \
-H "Content-Type: application/json" \
-d '{"email":"YOUR_OWN_EMAIL_ADDRESS","marketing_opt_in":false,"source":"docs"}'
The endpoint returns 202 and delivery status, never the raw key:
HTTP/1.1 202 Accepted
Content-Type: application/json
Cache-Control: no-store
{
"status": "verification_required",
"delivery": "email",
"message": "Confirm your inbox using the verification email before receiving a usable key.",
"free_key": {
"email_verification_required": true,
"price_usd": 0,
"card_required": false,
"account_required": false,
"calls_per_utc_day": 10,
"shared_across": ["brooks", "weis"],
"auth": { "header": "X-API-Key", "prefix": "qd_" }
},
"marketing_updates": "not_subscribed"
}
First confirm the one-hour, single-use verification link sent to your inbox. The key is
activated and displayed only after you press the confirmation button; a copy is emailed to
you. Signup and MCP responses never contain a key. marketing_opt_in is optional and defaults
to false; set it to true only when the email owner explicitly asks for product updates. A true
value records a pending request only. The recipient must reply SUBSCRIBE to the
delivery email before any marketing updates begin, so another person cannot subscribe an inbox
they do not control. The delivery email is a service message, not marketing consent.
A free key provides 10 successful responses per UTC day, shared across
Brooks, Weis, Max Pain and Gamma. Both the anonymous and free-key counters reset at 00:00 UTC.
Their responses carry free_tier.access_kind, calls remaining, reset time and the
correct next action. When either allowance runs out, the API returns 429
FREE_QUOTA_EXHAUSTED with Retry-After and
quota_resets_at. The anonymous error offers request_free_api_key;
the free-key error carries the reset time and a sentence to relay to the person. A source
address is hashed rather than stored for the anonymous counter, as the
privacy page spells out. The same two endpoints are also a remote MCP
server — per-client connect instructions on the MCP page.
Or connect over MCP
The same two endpoints are also a remote MCP server, for Claude Desktop, ChatGPT, Cursor, VS Code, Zed and anything else that speaks the Model Context Protocol. There is nothing to install — point the client at:
https://api.quantdata.uk/mcp
Transport is streamable HTTP. The MCP tools are the same two APIs behind another transport.
No authentication follows the same no-key rules as the HTTP API: one anonymous first-look
response per source, or a shared 100-per-platform daily pool when
the call arrives through a recognised AI-platform fetcher. The MCP access tool
quantdata_request_free_api_key requests a verification email and never returns a
key. The owner must open the email and press the confirmation button before free API access
is granted. Configure the key received after verification in the client using
X-API-Key for the shared 10-response UTC-day allowance. The tool
descriptions carry the same coverage limits and caveats as this page.
Free access
Verify your email to receive a free key. Brooks and Weis share 10 successful calls per UTC day. Research notes, studies and methodology are free to read without a key.
Max Pain and Gamma data services were discontinued on 14 October 2026. Their old API URLs return 410 Gone without charging or consuming free calls.
Quickstart
# Brooks price-action events in the freshest window
curl -H "X-API-Key: qd_9f3c..." \
https://api.quantdata.uk/v1/brooks/ES=F
# Weis Wave state, US day session
curl -H "X-API-Key: qd_9f3c..." \
https://api.quantdata.uk/v1/weis/ES=F
# Same instrument, Asian session (24h instruments only: =F futures, =X spot FX)
curl -H "X-API-Key: qd_9f3c..." \
"https://api.quantdata.uk/v1/weis/ES=F?session=asia"
# Full bar stream, including pre/post-market; matches the published Weis reference-rate window
# Requires a source with real extended-hours volume; otherwise returns 422 NO_EXTENDED_VOLUME
curl -H "X-API-Key: qd_9f3c..." \
"https://api.quantdata.uk/v1/weis/ES=F?session=full"
Endpoints
GET /v1/brooks/{symbol}
Rolling Brooks price-action events in the current window, each with the pre-registered
outcome rate measured for that window; range breakouts additionally carry a calibrated
per-event failure estimate. Use a key for ongoing calls. Definitions
and validation are described on the Brooks Events page.
The same response carries two further blocks: day_type, the five-class
day-type distribution from the model trained for the current window — the day session
(mode=day_session, published accuracy 66%/80% against a 37% baseline) and
the Asian window (mode=asia_session, trained natively on Asian windows,
pre-registered and confirmed zero-shot on NQ, with its own accuracy figures on its own
label taxonomy — the two sets are not comparable; the gap window has no validated model
and returns available: false) — and shape, a plain arithmetic
description of the bars already printed in the window.
| Parameter | In | Type | Default | Notes |
|---|---|---|---|---|
symbol | path | string | required | Ticker in any supported format. Normalised server-side — see symbol formats below. |
window | query | rth | gap | asia | freshest bars | rth is the market's own day session — 09:30–16:00 New York for US
symbols, local hours for Hong Kong and A-shares. gap
covers 16:00–21:30 Beijing — the stretch between the Asian close and the US
open. asia covers 09:00–16:00 Beijing. gap and
asia are limited to 24-hour instruments — futures
(=F), spot FX (=X) and crypto; anything else returns
400. Omit the parameter to follow whichever window has the freshest
bars.
|
{
"symbol": "ES=F",
"window": "gap",
"window_is_live": true,
"session": "2026-08-06",
"bars": 41,
"quote": {
"open": 6362.25, "high": 6371.5, "low": 6355.0,
"last": 6368.75, "prev_close": 6360.5, "change_pct": 0.13
},
"source": "leandata",
"volatility_base": {
"avg_daily_range_5d": 62.75,
"definition": "mean high-low range of the last 5 completed US regular sessions"
},
"events": [
{
"type": "range_breakout",
"direction": "up",
"at": "2026-08-06T18:05:00+08:00",
"bars_since": 6,
"breakout_level": 6366.0,
"range": { "low": 6358.25, "high": 6366.0 },
"outcome_so_far": { "failed_within_10_bars": null, "resolved": false, "bars_observed": 6 },
"conditional_estimate": {
"p_fail_within_10_bars": 0.58,
"meaning": "Of past breakouts whose own rolling context looked like this one, this share closed back through the breakout level within 10 bars."
},
"reference": {
"failure_rate_pct": {
"within_5_bars": { "rate_pct": 50.4, "n": 3918 },
"within_10_bars": { "rate_pct": 62.8, "n": 3916 },
"within_20_bars": { "rate_pct": 72.4, "n": 3882 }
}
}
}
],
"day_type": {
"available": false,
"why": "No day-type model has been trained or validated for the gap window. The day session has its own model (mode=day_session) and the Asian window has its own (mode=asia_session); the gap window is covered by the shape block and the event reads instead.",
"window_note": "Two validated day-type reads exist, each from a model trained on its own window ..."
},
"shape": {
"bars_measured": 41,
"net_move_pct_of_range": 61.2,
"close_position_in_range": 0.83,
"one_way_share": 0.58,
"deepest_pullback_pct_of_range": 24.5,
"high_made_at": 0.92,
"low_made_at": 0.04,
"direction_changes": 3,
"range_tightness": 0.74,
"how_to_read": "Arithmetic over the last 41 five-minute bars — what this stretch did, not what comes next ...",
"not_a_forecast": "Measured description of bars that have already printed. It carries no accuracy figure because nothing is being predicted, and it is not the day-type classification ..."
},
"reference_note": "Measured on ES futures 5-minute bars, 2010-2026, pre-registered.",
"disclaimer": "Educational analytics, not investment advice."
} | Field | Type | Meaning |
|---|---|---|
symbol | string | The normalised symbol the read was computed on. May differ from what you sent. |
window | string | The window the events were built on: rth, gap or asia. An unrecognised value such as eth returns 400 rather than quietly falling back. |
window_is_live | boolean | Check this before saying "now". true means the window is open and this read covers the window in progress. false means you are reading the last completed one; a window_note spells it out. |
session | string | The window-local trading date the read covers, YYYY-MM-DD. |
bars | integer | 5-minute bars inside the window so far. A complete US day session is 78; the gap window holds 66. |
quote | object | open, high, low, last, prev_close, change_pct for the window — so you can verify the read describes the stretch you think it does. |
source | string | Which market-data vendor served the bars. |
volatility_base | object | The 5-day average daily range every event definition is scaled by, with its definition spelled out. |
events | array | Each classical event detected in this window today (first occurrence per type), with direction, timing, live outcome status, the pre-registered reference rates for this window, and — on range_breakout — the calibrated conditional_estimate. An empty array means nothing has fired yet; that is a finding, not an error. |
day_type | object | The calibrated five-class day-type distribution for the session in progress. available is true only in the rth window; in gap and asia it is false with a why, because the model reads a session from its open and those windows have none. When available: bars_read, probs (five classes, descending, summing to 1), meaning, accuracy, and below 18 bars a provisional_note. This block is a prediction about an unfinished day and carries its measured accuracy with it. |
shape | object | Arithmetic over the bars already printed in this window: bars_measured, net_move_pct_of_range, close_position_in_range, one_way_share, deepest_pullback_pct_of_range, high_made_at, low_made_at, direction_changes, range_tightness, plus how_to_read and not_a_forecast. Present in every window including gap, and absent only when fewer than 10 bars have printed. It measures a stretch that already happened, so it has no accuracy figure and is not the day-type read. |
window_note | string (optional) | Present whenever window_is_live is false. Names the completed window being returned so it is not reported as live action. Relay it. |
window_progress_note | string (optional) | Present when the window just opened: the first range needs 10 completed bars plus a breakout bar, so no range event can exist earlier. |
coverage_note | string (optional) | Present on symbols other than ES, NQ and QQQ: the reference rates and the conditional model were measured and validated there, so elsewhere they are context, not claims. Relay it. |
symbol_note | string (optional) | Present when the ticker resolves but is a known confusion trap — for example GOLD, which is a US-listed equity (Gold.com, Inc.) and not the metal — for the metal use GC=F or GLD. |
disclaimer | string | Always present. Surface it; do not strip it. |
The example above is a gap read, so its day_type block is
unavailable by construction. In the rth window the same block looks like this:
{
"day_type": {
"available": true,
"mode": "day_session",
"bars_read": 54,
"probs": {
"TrendUp": 0.34,
"Reversal": 0.22,
"TrendingRange": 0.21,
"Range": 0.2,
"TrendDown": 0.03
},
"meaning": "Calibrated probability distribution over the five day types this session
could resolve into. Report the distribution, not just the top class — the
second script is often very much alive.",
"accuracy": "Measured on held-out S&P 500 E-mini years the model never trained on: at
18 bars (90 minutes in) top-1 53.5% and top-2 68.6%; over a complete
session 66% and 80%. The majority class covers 37% of days — quote that
baseline alongside any accuracy figure. Calibration error is under 10% ..."
}
}
Two different kinds of statement live in one response, and they should not be merged.
day_type estimates how an unfinished day resolves, so it is scored: 66% top-1
and 80% top-2 over a complete session, 53.5% and 68.6% at the 18-bar mark, against a 37%
majority-class baseline, on held-out ES years. shape measures bars that have
already printed, so there is nothing to score and no accuracy figure to borrow — it is not
a day type, and it says nothing about what comes next.
Only our own labels and probabilities are returned. No course material, slide titles or third-party text is ever exposed through this endpoint.
GET /v1/weis/{symbol}
Weis Wave structure and volume-price events for the instrument. Use a key for ongoing calls.
| Parameter | In | Type | Default | Notes |
|---|---|---|---|---|
symbol | path | string | required | Must be an instrument whose data source carries volume. Same formats as /v1/brooks. |
session | query | rth | asia | full | rth | rth is the US day session. asia is accepted only for 24-hour
instruments (=F, =X), otherwise 400; in practice
Weis needs exchange volume, so spot FX still returns its no-volume result.
full uses the whole bar stream, including pre- and post-market, and is the
window on which the published Weis reference win rates were measured. It is valid only
when the active source publishes real extended-hours volume. Otherwise the endpoint
returns 422 NO_EXTENDED_VOLUME; obey its retryable field, or
use rth.
|
{
"symbol": "ES=F",
"asof": "2026-07-24",
"session_window": "rth",
"session_is_live": true,
"source": "leandata",
"waves_total": 63,
"current_wave": {
"dir": "up", "bars": 7, "move": 12.25,
"volume": 41822.0, "vol_vs_median20": 0.63
},
"recent_waves": [
{ "dir": "down", "move": -21.75, "bars": 14,
"volume": 128640.0, "end_time": "2026-07-24 10:35:00-04:00" },
{ "dir": "up", "move": 18.5, "bars": 9,
"volume": 74210.0, "end_time": "2026-07-24 11:20:00-04:00" }
],
"events": [
{ "type": "no_supply_long", "dir": "long",
"time": "2026-07-24 12:15:00-04:00", "price": 6404.75,
"reference": {
"es_win": 0.514, "es_n": 23077, "es_p": "3e-5",
"verdict": "CONFIRMED(ES)",
"warning": "reverses on BTC (48.3%) — do not port across assets"
} }
],
"disclaimer": "Wave/event analytics derived from Weis Wave methodology. Reference stats are ES-futures-specific (2010-2026, pre-registered tests); edges do NOT transfer across assets (verified: BTC null/reversed). Educational analytics, not investment advice."
} | Field | Type | Meaning |
|---|---|---|
session_window | string | The window used: rth, asia, or full. |
note_window | string (optional) | On rth and full, states whether the returned wave construction matches the full-stream window used for the published reference win rates. |
note_session | string (optional) | On asia, states that the window is experimental and the reference win rates do not transfer directly. |
waves_total | integer | Completed waves detected in the fetched window. Event detection needs at least 46. |
current_wave.dir | string | up, down, or forming when no wave has been established yet. |
current_wave.bars | integer | Bars elapsed since the last confirmed pivot. |
current_wave.move | float | Price change since that pivot, in the instrument’s own units. |
current_wave.volume | float | Volume accumulated in the wave so far. |
current_wave.vol_vs_median20 | float | null | That volume divided by the median volume of the last twenty completed waves. Below 1.0 means this move is being made on less than normal effort. null when there is not enough history. |
recent_waves[] | array | The eight most recent completed waves, oldest first. Each has dir, move, bars, volume and end_time. |
events[].type | string | One of cib_long, cib_short, no_supply_long, no_demand_short, sot_short. |
events[].dir | string | long or short — the direction the tradition assigns to the event, not a recommendation. |
events[].time | string | Timestamp of the bar at which the event became knowable. Never earlier than the data that confirms it. |
events[].price | float | Close at that bar. |
events[].reference | object | Pre-registered statistics for that event type on ES 5-minute data, 2010–2026: es_win (win rate as a fraction), es_n (sample size), es_p (p-value where significant), verdict, and note or warning where the result reverses or fails to transfer. |
note | string (optional) | Present when there is wave structure but fewer than 46 waves, so no event can fire yet. |
error | string (optional) | Present instead of an analysis when the source has no volume or fewer than 120 bars. |
disclaimer | string | Always present, and it carries the cross-asset warning. Surface it. |
A maximum of ten events are returned, most recent last. Reference win rates hover between 45% and 52% and two of them run the opposite way from the tradition — that is the measured truth, not a bug. See the Weis Wave page for the full table and the pre-registration.
Symbol formats
Symbols are normalised server-side, so several common spellings resolve to the same instrument. The rules:
| Market | Format | Examples |
|---|---|---|
| US stocks and ETFs | Plain ticker | NVDA, TSLA, SPY, QQQ |
| US futures | Root plus =F | ES=F, NQ=F, GC=F, CL=F |
| Hong Kong | Digits plus .HK; leading zeros and an HK: prefix are both accepted, and codes shorter than four digits are padded to four | 3690.HK, 03690.HK, HK:3690 all resolve to 3690.HK; 700.HK and HK:700 resolve to 0700.HK |
| China A-shares | 6-digit code; the exchange suffix is inferred (6/9/5 leading digit → Shanghai, 0/1/2/3 → Shenzhen) | 600519, 600519.SH, SH600519, 600519.XSHG all resolve to 600519.SS |
| Crypto spot | Base and quote joined by a hyphen; USD, USDT, USDC, EUR and GBP quotes are accepted | BTC-USD, ETH-USD, SOL-USDT |
| Crypto perpetuals | Base plus -PERP; Kraken’s own PF_ code also works | BTC-PERP, SOL-PERP, PF_XBTUSD |
| Spot FX | Currency pair without a slash; 15 major currencies | EURUSD, eurusd, EUR-USD, EURUSD=X all resolve to EURUSD=X. EUR/USD does not work — the slash is a path separator, so it 404s before the symbol is parsed. |
Crypto bars come from Kraken, which publishes real per-bar volume; spot reads cover the last
60 hours and perpetuals about seven days. On thinly traded pairs many five-minute bars record
no trades at all, and every Weis condition is a volume ratio, so /v1/weis returns
422 TOO_ILLIQUID rather than waves built on gaps in trading. The response reports
the share of bars that traded. /v1/brooks is unaffected — its event definitions
use price only. Liquid pairs such as BTC-USD, ETH-USD and
SOL-USD traded in every bar when we checked. Note that no win rate has been
measured on any crypto pair: the pre-registered transfer tests on BTC confirmed none of eight
signals, and one reversed. Treat crypto reads as descriptive.
Spot FX works on /v1/brooks only, with a standing caveat in the response: the
reference rates and the conditional model were measured on ES, NQ and QQQ, so FX reads carry
a coverage_note and are descriptive. /v1/weis cannot run on spot FX at all
(no consolidated volume exists over the counter) — use the CME currency futures instead:
6E=F, 6B=F, 6J=F, 6A=F. The options
endpoints do not cover FX and there is no substitute to offer (see the prompt below for why
currency ETFs do not work either).
Never pass a plain English instrument name as a ticker. GOLD resolves — to a US-listed equity, Gold.com, Inc., not the metal. The API
flags that specific trap with a symbol_note, but the general rule matters more:
map the name to an instrument first. Gold is GC=F for futures,
GLD for the ETF or GC=F for futures; the S&P 500 is
SPY or ES=F; the Nasdaq is QQQ or NQ=F;
crude is CL=F or USO. A wrong ticker returns a 404 that
suggests the right one, and a 404 is never charged — so guessing is safe.
Errors
Every error is RFC 9457 application/problem+json — the standard shape, with
type, title, status and detail. Every body
additionally guarantees error_code and retryable. Access and billing
errors add richer handoff fields such as available_actions and
message_for_your_human; ordinary request and validation errors do not promise them.
The retry contract, which is the one thing worth memorising: retryable is always false on 401,
402 and 403, and none of the three should ever be retried. A
402 in particular will never succeed on retry — it is a billing state, and the
second call returns the same thing as the first. Branch on that one boolean and an agent
cannot get stuck hammering a closed door. Transient upstream failures, quota responses with a
reset time, and a temporary extended-volume source fallback can carry
retryable: true. Obey the field in the body instead of inferring from status
alone. Access errors also tell an agent when to request a free key and when to hand a
sentence to a person.
| Status | error_code | retryable | Meaning and what to do |
|---|---|---|---|
400 | REQUEST_FAILED | false | Two cases. Either you asked for ?session=asia on something that is not a 24-hour instrument — drop the parameter, or use a futures (=F) or spot FX (=X) symbol. Or you called an options endpoint with a symbol it does not cover: an index root, or a Hong Kong, A-share, futures, FX or crypto symbol. The message says which, and names the tracking ETF where one exists. |
422 | VALIDATION_ERROR | false | The request body or one of its fields failed validation — most often an invalid email or malformed free-key request. Fix the request; repeating it unchanged cannot work. |
422 | NO_EXTENDED_VOLUME | Read the body | session=full needs genuine pre/post-market volume, but the active source supplied mostly zero-volume extended bars. If retryable: true, the API temporarily fell back from a source that normally carries that volume: retry once. If false, use session=rth or a symbol/source with real extended-hours volume. |
401 | NO_API_KEYINVALID_API_KEY | false | The key is not in our records — check the header name and that the key starts with qd_; keys are case-sensitive and carry no whitespace. With no key, the normal path is the anonymous first look or, for recognised AI-platform fetchers, the shared platform pool; NO_API_KEY appears when a source counter cannot be assigned. The body includes request_free_api_key. |
429 | FREE_QUOTA_EXHAUSTEDFREE_QUOTA_EXHAUSTED_SERVICE_WIDE | true | Read access_kind: anonymous means the one first-look response is spent, and ai_platform means the shared no-key pool for recognised AI-platform fetchers (100 successful calls per platform per UTC day) is spent — in both cases the next action requests a free personal key. evaluation_key means the free key has spent its 10 shared calls and the next action is to wait for the reset. The body carries Retry-After and quota_resets_at. The service-wide variant protects capacity for keys bought earlier, which are exempt. A separate FREE_POOL_BURST 429 is a per-minute limit inside the platform pool — its Retry-After is under a minute and a short wait genuinely clears it. |
402 | SUBSCRIPTION_EXPIREDUSAGE_CAP_REACHED | false | The subscription behind the key has lapsed, or the anti-abuse cap for this billing period has been hit. Do not retry. The body says which case it is and carries a sentence to relay to the person who owns the key. It applies only to keys bought earlier. |
429 | FREE_KEY_REQUEST_LIMIT | true | The email or source has requested too many free-key delivery messages for the UTC day. The response does not reveal whether an address already exists. Wait for the reset; do not loop on the delivery endpoint. |
403 | PRODUCT_NOT_ENTITLED | false | The key is valid but is a legacy per-product key that does not cover this endpoint — for example an old Weis-only key calling /v1/brooks. Email quantdata@quantdata.uk; existing subscribers are moved across by hand rather than paying twice. Current keys cover every endpoint and never see this. |
404 | REQUEST_FAILED | false | No data for that symbol, or not enough sessions to compute a read. Failed lookups do not consume usage. Read the message: it suggests a likely correct ticker where there is one. |
503 | REQUEST_FAILED | true | Options endpoints only. The upstream option-data source could not be reached and we hold no cached snapshot for that symbol. Back off and try again. If a snapshot does exist you will get a 200 carrying stale: true instead — we serve a dated cached chain rather than fail. |
The extra fields
| Field | Type | Meaning |
|---|---|---|
error_code | string | Guaranteed on every error. Stable machine-readable identifier: NO_API_KEY, INVALID_API_KEY, PRODUCT_NOT_ENTITLED, SUBSCRIPTION_EXPIRED, USAGE_CAP_REACHED, FREE_QUOTA_EXHAUSTED, FREE_QUOTA_EXHAUSTED_SERVICE_WIDE, FREE_POOL_BURST, FREE_KEY_REQUEST_LIMIT, VALIDATION_ERROR, NO_EXTENDED_VOLUME, or REQUEST_FAILED for other request errors. Branch on this, never on the prose. |
retryable | boolean | Guaranteed on every error. false on 401, 402 and 403. It is true on transient 5xx responses and daily-limit 429 responses that carry a reset time; NO_EXTENDED_VOLUME may be either, depending on whether the source fallback is temporary. Obey the value literally. |
available_actions | array (access/billing errors) | Not guaranteed on ordinary request errors. When present, gives machine-readable next steps such as request_free_api_key, evaluate_for_free, subscribe, or ask_a_human. |
message_for_your_human | string (access/billing errors) | Not guaranteed on ordinary request errors. When present, it is a pre-written sentence for the model to relay verbatim to its user. |
free_without_a_key | array | The one anonymous API first look plus the free Max Pain pages, GEX pages and methodology page. For continued API evaluation, use the separate request_free_api_key action. |
what_you_get | string | A plain description of what the API data is and is not — descriptive statistics about how similar past sessions resolved, not a forecast and not a recommendation. |
doc_url | string | A link back to this page. |
A full body — this is a 401 with no key sent at all:
HTTP/1.1 401 Unauthorized
Content-Type: application/problem+json
{
"type": "https://quantdata.uk/problems/no-api-key",
"title": "No API key supplied",
"status": 401,
"error_code": "NO_API_KEY",
"retryable": false,
"detail": "Request a free API key by email at /v1/access/free-key, then send it in the
X-API-Key header. Keys start with qd_.",
"doc_url": "https://quantdata.uk/docs",
"what_you_get": "Descriptive statistics and model output: how sessions that resembled the
current one resolved historically. It is not a forecast of today, not a
recommendation to buy or sell anything, and not investment advice.",
"free_without_a_key": [
{ "url": "https://api.quantdata.uk/v1/{endpoint}/{symbol}", "cost_usd": 0,
"returns": "One anonymous first-look response per source per UTC day. After that,
request a free qd_ key by email for 10 successful calls
per UTC day shared across both APIs." },
{ "url": "https://quantdata.uk/methodology", "cost_usd": 0,
"returns": "How every number on this service was measured: held-out years,
pre-registration, calibration, and the fact-check of the trading
literature's famous probability claims against 3,946 S&P 500 futures
trading days. Read this before deciding whether the endpoints are
worth anything to you." }
],
"available_actions": [
{ "action": "request_free_api_key", "method": "POST",
"url": "https://api.quantdata.uk/v1/access/free-key", "content_type": "application/json",
"body_schema": {
"email": "required email address",
"marketing_opt_in": "optional boolean; true only with explicit consent; the recipient must
reply SUBSCRIBE to the delivery email before updates begin",
"source": "optional short attribution string"
},
"delivery": "The qd_ key is sent only to that email address.",
"cost_usd": 0, "card_required": false,
"calls_per_utc_day": 10,
"covers": ["brooks", "weis"] },
{ "action": "evaluate_for_free", "method": "GET", "url": "https://quantdata.uk/methodology",
"cost_usd": 0,
"why": "Every number this service returns, with how it was measured and where the
models fail. Read it before deciding whether the endpoints are worth
anything to you." },
{ "action": "ask_a_human", "mailto": "quantdata@quantdata.uk" }
],
"message_for_your_human": "This endpoint needs a Quant Data API key. I can request a free one
using your email address; no account, card or GUI is required, and
both endpoints share 10 successful calls per
UTC day. The raw key is sent only to that inbox. The output is
historical statistics, not a forecast or trading recommendation.",
"disclaimer": "Educational analytics, not investment advice."
} Long strings are wrapped here for reading; the real payload sends them on one line. Every status at a glance:
HTTP 429 FREE_QUOTA_EXHAUSTED retryable TRUE anonymous, platform-pool or free-key daily allowance used up; inspect access_kind
HTTP 429 FREE_QUOTA_EXHAUSTED_SERVICE_WIDE
retryable TRUE service-wide evaluation-traffic cap; keys bought earlier are exempt
HTTP 429 FREE_POOL_BURST retryable TRUE per-minute limit inside the shared platform pool; Retry-After is under a minute
HTTP 429 FREE_KEY_REQUEST_LIMIT retryable TRUE free-key delivery limit reached for email/source/day
HTTP 401 NO_API_KEY retryable false no key, and the source address could not be read
HTTP 401 INVALID_API_KEY retryable false the key is not in our records
HTTP 403 PRODUCT_NOT_ENTITLED retryable false a legacy key that does not cover this endpoint
HTTP 402 SUBSCRIPTION_EXPIRED retryable false the subscription behind this key has lapsed
HTTP 402 USAGE_CAP_REACHED retryable false anti-abuse cap hit for this billing period
HTTP 422 VALIDATION_ERROR retryable false malformed free-key body or invalid field value
HTTP 422 NO_EXTENDED_VOLUME retryable varies session=full source lacks real pre/post-market volume; obey retryable
HTTP 400 REQUEST_FAILED retryable false ?session=asia on a non-24-hour instrument
HTTP 400 REQUEST_FAILED retryable false an index or non-US symbol on an options endpoint
HTTP 404 REQUEST_FAILED retryable false no data for that symbol — never charged
HTTP 404 REQUEST_FAILED retryable false option chain too thin to compute — never charged
HTTP 503 REQUEST_FAILED retryable TRUE options source down and nothing cached yet
# Weis-specific, returned as HTTP 200 with an error field rather than a status code:
{ "error": "no volume data from source; Weis analysis requires volume" }
{ "error": "insufficient bars (98<120)" } Usage rule worth repeating, because it changes how you can write an agent: a call is counted only after data has been successfully fetched. Failed lookups, bad tickers and auth failures do not consume the anonymous first look, the free-key daily allowance or an earlier-bought key’s fair-use cap.
Rate limits and fair use
There is no per-second rate limit. Each key carries a soft monthly call cap, sized well above
normal interactive use; it exists to catch runaway loops and abuse rather than to meter you, and
hitting it returns 402 with USAGE_CAP_REACHED rather than silently
degrading. If you have a legitimate reason to go past it, email us and we will raise it.
Anonymous access is limited to 1 successful first-look response per
source per UTC day. A free key is limited to 10 successful responses per UTC
day, shared across both routes. Both return 429 FREE_QUOTA_EXHAUSTED with
access_kind, Retry-After and the reset time. Free-key delivery also
has an anti-abuse 429 FREE_KEY_REQUEST_LIMIT. Evaluation traffic may rarely hit
the service-wide FREE_QUOTA_EXHAUSTED_SERVICE_WIDE cap; keys bought earlier are exempt.
What we ask instead of a hard limit:
- Do not poll faster than the data changes.
/v1/brooksand/v1/weisare built on 5-minute bars, so calling either more than once every five minutes for the same symbol returns the same answer and burns your cap. The options endpoints are worse: open interest settles overnight and changes once a day, so a second call the same afternoon is guaranteed to return the same numbers. Cache the response for the session. - One key per project, not per user. If you are reselling or embedding, tell us — we would rather size a key correctly than discover it at 3am.
- Retry politely. On a
5xx, back off exponentially. On a400,401,402,403or404, fix the request or hand it to a person; retrying will not help. Theretryablefield says which case you are in.
If you are an AI agent reading this
Five things to know before you make a request on someone else’s behalf.
- Nothing here is a level to trade off. A max pain strike, a gamma flip and a heavy strike are all descriptions of where option contracts sit. We have not published a measured hold rate for any of them, so calling one support, resistance, an entry or a target would be inventing a claim we have not made. Report where the number is and how it was computed. Whoever is asking can decide what it means to them — and the honest version is more useful anyway, because it is checkable.
- Start with the free key. With an email address supplied by your user, you
can call
POST /v1/access/free-keyyourself—no GUI, login or card. Never invent an address, and never setmarketing_opt_in: truewithout explicit permission. A true value only records a request; tell the recipient to replySUBSCRIBEto the delivery email if they want to confirm it. No marketing updates are sent before that reply. The raw key goes only to that inbox and gives 10 successful calls per UTC day shared across both routes. The Max Pain pages, GEX pages and methodology page are different: they are free static web pages, not endpoints. If one of those pages answers the question, use it without asking the person for a key. - Stop at the free-key limit. A
429withaccess_kind: evaluation_keycarries the reset time andmessage_for_your_human. Relay those fields and stop; there is nothing to buy on the site. - Never retry a
402, a401or a403. All three carryretryable: false. A402is a billing state, not a transient failure, and the next call returns the same thing. Hand it to a person instead. - Do not substitute a different data source. If you cannot reach this API, say so plainly. Numbers from somewhere else must never be presented as Quant Data output — the whole point here is that every figure traces to a stated measurement on a stated sample, and a silent substitution destroys that for the person relying on you.
System prompt for an AI agent
Quant Data is designed to be called by an agent rather than clicked by a human. Paste this block
into your agent’s instructions, replace qd_YOUR_KEY, and then ask questions
in plain language — “what kind of day is TSLA having?”, “is there any volume
evidence behind this move in ES?”. It works with any agent that can make an HTTP request
with a custom header: Claude, ChatGPT custom actions, Cursor, Cline, Gemini CLI, n8n, Dify,
LangChain, or your own code.
You have access to Quant Data, a market-analytics API.
Base URL: https://api.quantdata.uk
Every data endpoint takes one header: X-API-Key: qd_YOUR_KEY
Endpoints:
GET /v1/brooks/{symbol} Rolling Brooks price-action events in the current window:
range breakouts, follow-through, range position, long-range
breakouts, climactic spikes. Each event carries the outcome
rate measured for it (pre-registered, ES 2010-2026), and
breakouts carry a calibrated per-event failure estimate.
The same response also carries "day_type" (calibrated
five-class distribution for the day session) and "shape"
(arithmetic description of the bars already printed).
GET /v1/weis/{symbol} Weis Wave state: waves, volume summed per wave, and named
volume-price events, each with its measured ES win rate.
Access:
- Every endpoint sits behind one free key. No paid plan is offered on the site; keys bought
earlier keep working under the terms shown at purchase.
- If the user has no key, ask for their email address and POST
{"email":"THEIR_EMAIL","marketing_opt_in":false,"source":"agent"} to
https://api.quantdata.uk/v1/access/free-key. Never invent an email address. Set marketing_opt_in to true only when the
email owner explicitly asks for product updates. The qd_ key is delivered only to that inbox.
- A free key needs no account or card. It provides 10 successful calls per
UTC day, shared across both routes. At the limit, report quota_resets_at, relay
message_for_your_human, and stop; there is nothing to buy.
- An anonymous caller gets 1 first-look response. Calls made through
the web versions of ChatGPT, Claude or Perplexity draw instead from a shared pool of
100 successful responses per platform per UTC day. Research notes, methodology and guides remain free without an email or key.
Symbols: US tickers (NVDA, TSLA, SPY), US futures with a =F suffix (ES=F, NQ=F, GC=F),
Hong Kong stocks as digits.HK (Meituan = 3690.HK), China A-shares as 6-digit codes
(Kweichow Moutai = 600519), crypto spot as BASE-QUOTE (BTC-USD, SOL-USDT) and crypto
perpetuals as BASE-PERP (BTC-PERP), spot FX as a currency pair
(EURUSD or EUR-USD, both normalise to EURUSD=X). A spot-gold request (XAUUSD) answers
with the XAUT-USD token reading plus a note saying so — spot gold itself has no licensed
feed here; the metal with exchange volume is GC=F. Never write a pair with a slash:
EUR/USD is two path segments and returns 404 before the symbol is ever parsed.
Resolve an instrument name to a ticker before calling: gold -> GC=F (futures) or GLD
(ETF), S&P 500 -> SPY or ES=F, Nasdaq -> QQQ or NQ=F, crude oil -> CL=F or
USO, Bitcoin -> BTC-USD, euro -> EURUSD. Tokenised gold is its own instrument, not a
stand-in for the metal: XAUT-USD reads the Tether Gold order book on a crypto exchange,
which trades at a shifting discount to spot and does not follow the gold market's
calendar. Send it only when the user asked for the token.
Never pass a plain English name as a ticker: "GOLD" is a US-listed equity (Gold.com,
Inc.), not the metal. If you are unsure, try the ETF; a 404 comes back with a suggestion
and is never charged.
Rules:
- When asked what a market is doing right now or what just happened structurally, call
/v1/brooks first. Report events with their measured rates as historical frequencies,
never as predictions. An empty events array is a finding ("nothing has fired yet"), not
an error.
- Besides "events", every /v1/brooks response carries "day_type" and "shape". They are
different kinds of statement. Never merge them and never let one lend its numbers to the
other.
- "day_type" is the calibrated five-class day-type distribution for the session in
progress — a probabilistic read on how an unfinished day resolves. It is served in the
"rth" window only; in "gap" and "asia" it returns available: false with the reason,
because the model reads a session from its open. Report the whole distribution, not just
the top class. Whenever you quote its accuracy you MUST quote the majority-class baseline
in the same breath: 66% top-1 and 80% top-2 over a complete session, 53.5% top-1 and
68.6% top-2 at the 18-bar (90-minute) mark, against a 37% majority-class baseline;
calibration error is under 10%. Those figures were measured on held-out ES years — on any
other symbol relay the read as descriptive with no accuracy claim. Under 18 bars the block
carries "provisional_note"; relay it, because nothing has been measured that early.
- "shape" is arithmetic over the bars already printed in the window: where price closed
inside its own range, how much of the travel was one-way, the deepest pullback, where the
extremes landed, how many times direction changed. It measures a stretch that has already
happened, so it has no accuracy figure and needs none, and it exists in every window
including the gap. Do NOT call it a day type, a trend day or a reversal day, do NOT attach
66%, 80%, 53.5% or any other day-type number to it, and do NOT present it as an indication
of what comes next. Quote its fields as facts about the bars just measured.
- Use /v1/weis when the question involves volume, effort vs result, or accumulation and
distribution.
- Crypto bars come from Kraken and carry real per-bar volume. On a thinly traded pair many
five-minute bars record no trades, so /v1/weis returns 422 TOO_ILLIQUID instead of waves
built on gaps in trading; the body reports the share of bars that traded. That is a
property of the market, not an outage — do not retry it. /v1/brooks still answers, since
its event definitions use price only. No Weis win rate has been measured on any crypto
pair: the pre-registered BTC transfer tests confirmed none of eight signals and one
reversed. Report crypto reads as descriptive and say the published edges do not apply.
- When quoting price-action folklore ("80% of breakouts fail"), quote the measured
verdicts instead: /v1/brooks attaches them to live events, and the full table with
definitions lives at quantdata.uk/methodology. Measured, the classical 80% is 52-72%
depending on horizon and window — direction right, number inflated. Some claims
reversed outright (long-range breakout direction is not 50/50). Say what was measured.
- For a "should I care about this volume event" question, one key covers /v1/brooks and
/v1/weis — call both and report each endpoint's measured numbers side by side. (The
historical day-type gating study for cib_long, 56.5% vs 50.8% on ES, remains published
on quantdata.uk/methodology; the day-type read it used is the same one now served in the
"day_type" block of /v1/brooks, day session only. Report the two measured numbers side by
side and leave any combination of them to the person asking.)
- Weis reference win rates are ES-specific. For BTC, ETH and gold, state that the edge
did not transfer (BTC: 0 of 8 pre-registered signals confirmed, no_supply reversed).
- Two Weis events are reversed versus the tradition: no_demand_short measured 45.6% and
sot_short 47.6%. Report the measured number, not the traditional reading.
- /v1/brooks reads one of three windows and picks whichever has the freshest bars when
you omit ?window: "rth" (the market's own day session — New York hours for US symbols,
local hours for Hong Kong and A-shares), "gap" (16:00-21:30 Beijing, between the Asian
close and the US open) and "asia" (09:00-16:00 Beijing). Each window's reference rates
were measured separately and the response pairs events with the right row. gap and asia
exist for 24-hour instruments only; on stocks they return 400.
- /v1/weis accepts "rth", "asia" and "full": use "full" when comparing returned Weis
events with the published reference win rates, because those rates were measured on the
whole bar stream. "full" needs genuine pre/post-market volume and can return 422
NO_EXTENDED_VOLUME; obey retryable, then use rth when the source cannot provide it.
Unknown session or window values return 400 rather than quietly falling back.
- ALWAYS check "window_is_live" before you use the word now. When it is false you are
holding the last completed window; a "window_note" field spells it out — relay it and
date the read.
- A window that just opened carries "window_progress_note": the first range needs 10
completed bars plus a breakout bar, so the earliest possible event is about an hour in.
- The per-event "conditional_estimate" on range breakouts is calibrated and validated
zero-shot on NQ 2023+ (AUC 0.646, calibration error 5.1%, n=2,637) and on QQQ. On
symbols other than ES, NQ and QQQ, relay it as descriptive, with the "coverage_note"
the response carries.
- /v1/weis does not work on spot FX. Currency pairs are traded over the counter, so no
source publishes a consolidated volume and the wave construction has nothing to measure.
The response says so and is not charged. The CME currency futures do carry exchange
volume: EURUSD -> 6E=F, GBPUSD -> 6B=F, USDJPY -> 6J=F, AUDUSD -> 6A=F.
- Reference rates were measured on ES futures. On any other symbol the response carries a
"coverage_note"; relay it — the rates are context about ES, not claims about that
symbol.
- Errors are application/problem+json. Every problem carries error_code and retryable;
access and billing errors additionally carry available_actions and
message_for_your_human. Branch on error_code, never on the prose. "retryable" is false on
401, 402 and 403 — none can be fixed by calling again. Never retry a 402: it is a billing
state and will return 402 again.
- On a 401, relay "message_for_your_human" and offer to request the free key using the
request_free_api_key action. Ask for the user's email first; never invent one.
- On a 429, check access_kind. If it is anonymous or ai_platform (the shared no-key pool for
calls made through an AI platform), offer request_free_api_key. If it is
evaluation_key, relay the reset time and checkout_url. On a 402 from a paid key, relay
"message_for_your_human" and the checkout_url. You cannot finish checkout
yourself — card authentication (3-D Secure) requires the cardholder. Three web pages still
work without an email or key: quantdata.uk/notes, quantdata.uk/research, and quantdata.uk/methodology, which carries the fact-check
verdicts as a static table.
- If you cannot reach this API, say so. Never answer from a different data source and
present it as Quant Data output.
- This is educational statistics, not investment advice. Never present it as a buy or
sell recommendation, never state a probability as a certainty, and never size a
position. The rules in that prompt are not decoration. The session-hint rule stops an agent silently reading the wrong trading window; the cross-asset rule stops it quoting an ES win rate at someone asking about Bitcoin; the reversed-events rule stops it repeating folklore our own data contradicts. If you trim the prompt, keep those three.
Get a key
Start with a free API key: one email address, no login or card, and
10 successful calls per UTC day shared across both endpoints. The raw
qd_ key is delivered only by email; the request response never exposes it. An AI
agent can make that request after asking for the address.
Get free API key How access works
No paid plan is offered on the site; keys bought earlier keep working under the terms shown at purchase. The static Max Pain and GEX web pages stay free and need no key, and so does the methodology page. Questions, or a key bought under an earlier plan? quantdata@quantdata.uk — a person reads it.
Free daily notes by email
One email per publishing day: what the paid research publishers said, ticker by ticker. Free; unsubscribing deletes the address.