No key, no signup. This works right now, from anywhere:
curl 'https://cuckootrade.com/api/v1/alpaca/v2/stocks/bars?symbols=AAPL,CRASH&timeframe=1Day&start=2026-07-01'
Using alpaca-py? Pass
url_override="https://cuckootrade.com/api/v1/alpaca" and any
non-empty strings as API keys — they're never checked. The API is also
self-describing: /api returns a machine-readable index,
and /llms.txt summarizes everything for AI coding
tools.
Every endpoint lives under /api/v1/, and provider-compatible
surfaces add the provider's name to the path:
/api/v1/{provider}/<the provider's own path> ← mimics that provider's wire format
/api/v1/<endpoint> ← CuckooTrade-native (e.g. /api/v1/stream)
Everything through the provider segment is CuckooTrade's namespace;
everything after it replicates the provider exactly, which is why a
client only needs its base URL changed. The v2 in
/api/v1/alpaca/v2/stocks/bars is Alpaca's own version, not
ours. Three providers are live:
| provider | base URL | stands in for |
|---|---|---|
| alpaca | https://cuckootrade.com/api/v1/alpaca | data.alpaca.markets |
| alphavantage | https://cuckootrade.com/api/v1/alphavantage | www.alphavantage.co |
| polygon | https://cuckootrade.com/api/v1/polygon | api.polygon.io |
Two things are versioned, on purpose: the path version
(v1) covers the API surface — paths, parameters, error
shapes — while the generation parameter covers the
data itself. A surface change bumps the path; a data change becomes a new
generation, and old generations stay queryable (see
determinism).
Alpaca-compatible historical OHLCV bars. Regular sessions only (09:30–16:00 ET, NYSE calendar — weekends and US market holidays have no bars; half-days trade as full sessions). Only completed bars are served, so any response you receive is permanently reproducible.
| param | meaning |
|---|---|
| symbols | required; comma-separated, max 50. Any well-formed symbol works: curated tickers (AAPL, SPY, …) sit at plausible price levels, everything else gets a stable hash-derived personality. |
| timeframe | [1-59]Min, [1-23]Hour, 1Day, 1Week, [1,2,3,4,6,12]Month. Default 1Day. Coarser bars aggregate exactly from finer ones. |
| start / end | RFC-3339 or YYYY-MM-DD, inclusive. Defaults: last 30 days. Weeks/months clipped by the window are dropped, not served partial. |
| limit | total bars across all symbols, 1–10000 (default 1000). More data ⇒ next_page_token; pass it back as page_token. |
| sort | asc (default) or desc. |
| seed | cuckoo extension. Any string; returns a different but equally deterministic dataset with the same statistical character. Omit it for the shared default dataset. |
| generation | cuckoo extension. Pins the generator version (currently 1). Responses carry X-Cuckoo-Generation. |
| adjustment, feed, asof, currency | accepted and ignored, for Alpaca client compatibility. No corporate actions exist in generation 1. |
Also available: /api/v1/alpaca/v2/stocks/{symbol}/bars
(single symbol) and
/api/v1/alpaca/v2/stocks/bars/latest?symbols=… (last
completed bar). Errors are Alpaca-shaped
{"code", "message"}, and every message states the valid
grammar plus a working example.
Alpha Vantage's single-endpoint format, faithfully — including its
quirks: values are strings, series are keyed newest-first, and errors
come back as HTTP 200 with an "Error Message" body,
because that is how the real API reports them. apikey
accepts anything, including nothing. Intraday is a premium endpoint
on Alpha Vantage's free tier; here it's free like everything else.
curl 'https://cuckootrade.com/api/v1/alphavantage/query?function=TIME_SERIES_DAILY&symbol=IBM'
| param | meaning |
|---|---|
| function | required: TIME_SERIES_INTRADAY, TIME_SERIES_DAILY, TIME_SERIES_WEEKLY, TIME_SERIES_MONTHLY, or GLOBAL_QUOTE. |
| symbol | required; one symbol. Scenario tickers work: symbol=CRASH. |
| interval | intraday only, required there: 1min, 5min, 15min, 30min, 60min. Labels are interval-end times in US/Eastern. |
| outputsize | compact (default, last 100 points) or full (~20 years daily; trailing 30 days intraday). |
| month | intraday only: YYYY-MM serves that calendar month. |
| apikey, adjusted, extended_hours | accepted and ignored. |
| seed / generation | cuckoo extensions, same semantics as everywhere else. |
Two deliberate deviations from the real thing: only completed bars are served (no partial current day/week/month row — determinism requires it), and sessions are regular-trading-hours only.
Polygon's aggregates format: the same envelope
(ticker, queryCount, resultsCount, adjusted, results, status,
request_id, count), bars as {v, vw, o, c, h, l, t, n}
with t in Unix milliseconds, next_url cursor
pagination, and {"status": "ERROR"} error bodies.
apiKey is accepted and ignored, and status is
always OK — synthetic data is never delayed.
curl 'https://cuckootrade.com/api/v1/polygon/v2/aggs/ticker/MSFT/range/1/day/2026-07-01/2026-08-01'
| path / param | meaning |
|---|---|
| range/{multiplier}/{timespan} | minute (1–59), hour (1–23), day, week, month (1, 2, 3, 4, 6, 12), quarter (1, 2, 4), year. |
| {from} / {to} | YYYY-MM-DD (inclusive dates) or Unix millisecond timestamps. |
| sort | asc (default) or desc. |
| limit | 1–50000 (default 5000); past it, follow next_url. |
| adjusted, apiKey | accepted and ignored — no corporate actions exist, so adjusted and unadjusted are the same numbers. |
| seed / generation | cuckoo extensions; carried through next_url automatically. |
Also available:
/api/v1/polygon/v2/aggs/ticker/{ticker}/prev — the previous
session's daily bar, with Polygon's extra "T" field.
request_id is a hash of the request rather than a random
id, so identical requests stay byte-identical.
Every bar is a pure function of (symbol, timestamp, generation, seed).
Identical requests return identical bytes, forever, within a generation —
there is no database and no randomness at request time, which is also why
fully-specified historical responses ship
Cache-Control: immutable. If the generator ever improves,
that becomes generation 2; generation 1 stays queryable, so committed
fixtures never break.
Reserved symbols with scripted, calendar-anchored behavior. Every pattern appears within any 30-day window, so a demo or test never catches a quiet stretch. Responses stay schema-valid — the stress is in the values, never in malformed fields.
| ticker | behavior |
|---|---|
| CRASH | sharp ~25% crash mid-month, slow grind recovery |
| MOON | parabolic run-up peaking late in the month, hard correction |
| FLAT | zero-range bars pinned at $100.00 — breaks naive autoscaling |
| GAPPY | ±5–15% overnight gaps most days, quiet sessions |
| HALTS | minute bars absent during intraday halt windows |
| STALE | feed freezes mid-session: price repeats, volume is zero, timestamps keep advancing |
| SPIKEY | single-minute wicks that spike and instantly revert |
| PENNY | ~$0.30 prices, four decimals, high volatility |
| CHOPPY | high volatility, zero net drift |
| SPLITS | 2:1 forward split monthly — prior closes halve once it goes ex (see restatement) |
| DIVVY | monthly dividend whose ~1.5% adjustment lands five sessions late |
| REVISED | a bad print that stays in history until the exchange busts the trade |
Server-sent events — curl-able, no WebSocket library needed. It's what
drives the live ticker at the top of every page here. Two clocks:
clock=demo (default) is an always-open synthetic session whose
price is a pure function of wall time, so every viewer sees the same tick
at the same instant; clock=real follows the NYSE calendar and
emits completed 1-minute bars, staying silent (heartbeats only) while the
market is closed. Max 10 symbols and 5 concurrent streams per address;
streams close after 15 minutes — reconnect freely.
curl -N 'https://cuckootrade.com/api/v1/stream?symbols=CUCKOO,CRASH'
Real feeds rewrite history. A split or a late dividend adjustment restates bars you already stored, so the same request today and next month does not hand back the same bytes. If you keep bars in a database, that is the case your reconciliation job exists to catch — and the one nothing else will let you rehearse.
as_of models it without giving up determinism. It is a
second axis, not a loophole: pin as_of and the bytes
are frozen forever, exactly as the
determinism guarantee promises. Omit it —
the default — and you get what the feed would say today, which for a
restating ticker is not what it said last month. Because the schedule
is calendar-anchored, you can test a restatement by moving
as_of across the date instead of waiting for one:
# before the split processes curl 'https://cuckootrade.com/api/v1/alpaca/v2/stocks/bars?symbols=SPLITS&timeframe=1Day&start=2026-06-01&end=2026-06-30&as_of=2026-07-09' # after — same window, same request, every close halved curl 'https://cuckootrade.com/api/v1/alpaca/v2/stocks/bars?symbols=SPLITS&timeframe=1Day&start=2026-06-01&end=2026-06-30&as_of=2026-07-13' # and the ledger that says why curl 'https://cuckootrade.com/api/v1/corporate-actions?symbols=SPLITS,DIVVY'
| param | meaning |
|---|---|
| as_of | answer as the feed would have on this date (RFC-3339). Defaults to now. Not Alpaca's asof, which is a symbol-mapping date and is still accepted and ignored. |
| adjustment | raw (as-traded, never restated), split, dividend, or all. Defaults to all — Alpaca defaults to raw, and this is the one place we deviate, because it is observable only on the three tickers above. |
GET /api/v1/corporate-actions?symbols=… lists every action
with its announce_date, ex_date and
process_date. The gap between the last two is the point of
DIVVY: its adjustment lands five sessions after the ex-date,
well after a job that polls on the ex-date has decided the month is
settled. Requests for these tickers without an explicit
as_of are never marked immutably cacheable, for the obvious
reason.
Not seeing a change? Every bar response carries
X-Cuckoo-As-Of and X-Cuckoo-Restated — read
them with curl -i. 2 actions applied (SPLITS split ex
2026-07-10; …) means it worked; 0 actions applied
means nothing rewrote these bars. The usual cause is a window sitting
after every ex-date, because an action only rewrites bars dated
before it — query June and move as_of across July, not the
other way round. Note also that omitting start/end
defaults to the last 30 days, which is almost always the wrong window for
this.
Scope, stated plainly: this models the restatement, not the ex-date price discontinuity — the split rewrites the history in front of it, but you won't see the price halve on the ex-date itself. Corporate actions older than six months count as already baked into history.
The scenario tickers break the data. This breaks the
transport: sockets that die mid-frame, bodies that arrive
half-written, requests that fail twice before they work. Add
scenario= to any request. Nothing here ever fires unless you
ask for it — and because every fault is deterministic, a test that
passes once passes every time, which is the whole reason these belong in
CI rather than in a chaos dashboard.
| effect | where | what happens |
|---|---|---|
| flap:N | bars | fails N times, then succeeds — the one that tests your retry logic recovers |
| status:CODE | bars | returns that status, in the error shape of whichever provider you called |
| slow:MS | both | delays the response, or each frame |
| truncate | both | full Content-Length, half a body; on the stream, one frame cut mid-JSON while the connection lives on |
| drop:S | stream | closes the socket at S seconds, mid-frame, with no close event |
| garbage:N | stream | N unparseable frames mixed in with the good ones |
| silent:S | stream | no data and no heartbeats for S seconds — finds read timeouts |
# fails twice, succeeds on the third attempt curl 'https://cuckootrade.com/api/v1/alpaca/v2/stocks/bars?symbols=AAPL&timeframe=1Day&scenario=flap:2' # the socket dies twenty seconds in curl -N 'https://cuckootrade.com/api/v1/stream?symbols=CUCKOO&scenario=drop:20s'
Faulted responses are always Cache-Control: no-store and echo
X-Cuckoo-Scenario. One caveat worth knowing:
flap has to count attempts, and that counter lives per pod —
across our replicas a flap:2 can burn up to four failures
before it clears. If you need the count to be exact, run the container
yourself (see self-hosting).
60 requests per minute sustained per address, with burst headroom to 120 —
no key required. Responses carry
RateLimit-Limit / -Remaining / -Reset headers, and a 429
tells you exactly how long to back off. Limits are enforced per replica,
so the effective ceiling may be somewhat higher than advertised.
The API is stateless and MIT-licensed. If your CI pipeline shouldn't
depend on an external service, run your own instance: clone
the repo and
start it with pip install -r api/requirements.txt &&
python api/api.py, or pull the prebuilt container image published
alongside the repo. Determinism means a local instance serves
byte-identical data to cuckootrade.com for the same generation.
X-Cuckoo-Synthetic: true. CuckooTrade exists to exercise
code paths — development, CI, demos, teaching. It is not market data,
must never inform a real trade, and is not a backtesting tool: a strategy
that profits against synthetic data has learned the generator, not the
market.