Base URL /v1. Authenticate with your key in the
X-API-Key header. Every data response carries the age of the
facts inside it.
Every data endpoint returns the same wrapper. Read stale
before you read data.
{
"data": [ ... ],
"as_of": 1785626383.0, // newest fact in the payload
"age_s": 42.1, // how old that is, right now
"stale": false, // computed, never asserted
"stale_after_s": 600,
"source": "live",
"served_at": 1785626425.1
}
age_s is measured from the data, not from when we answered.
If as_of is null, stale is true — an
unknown age is treated as a bad one, because a feed that stops reporting
its own recency is exactly the failure this guards against. Free-tier
responses add delayed_s; those numbers are real, just behind.
GET /v1/flowCohort flow per coin over an arbitrary window.
curl -H "X-API-Key: $KEY" \ "https://your-host/v1/flow?window=4h&coins=ETH,BTC"
window accepts 90m, 4h,
3d — anything up to 8 days. Arbitrary windows are
possible because every event carries its own timestamp; sources built on
pre-bucketed aggregates can only offer fixed 24h/7d/30d.
{
"data": [{
"coin": "ETH",
"long_usd": 4821904.11, // opened/added long, still held
"short_usd": 1204551.02,
"net_usd": 3617353.09,
"wallets": 213, // distinct cohort wallets
"bias": 0.8002 // long / (long + short)
}],
"totals": { ... }, "by_segment": { ... }
}
GET /v1/flow/series
Rolling bias and net flow over time. This is the endpoint the product
exists for. ?window=24h&coin=ETH&resolution=20s.
{
"data": [{"t": 1785626380, "bias": 0.71, "net_usd": 812004.2, "wallets": 41}],
"resolution_s": 20, // what you actually got
"native_resolution_s": 20,
"rolling_recency_s": 300, // how far back each point looks
"points": 4320
}
resolution is how often you get a reading: 20s on Desk,
60s on Build, 900s on Free. Ask for finer than your plan allows and the
response is clamped, with resolution_clamped, a reason, and
the plans that would serve it. A clamped bucket is a genuine aggregate
over the wider interval, never a smoothed one.
recency is how far back each point looks, and defaults
to a rolling window of window/48. Every point is
recalculated as of its own timestamp using only the data that existed
then — so a wallet that closed at 14:00 does not retroactively
vanish from the 09:00 point. This is also why the series cannot be
rebuilt from periodic snapshots: a wallet that opened and closed between
two snapshots was never observed at all.
GET /v1/wallet/{address}
Positions across both dexes, PnL by period, leverage, distance to
liquidation, and both cohort badges. Size and profitability are
independent classifications; a wallet carries one of each. An address
with no perp account returns exists: false — a fact,
not an error. Also /v1/wallet/{address}/fills.
GET /v1/leaderboard
?kind=flow ranks by dollars of position change in the
last four hours, at full resolution. kind=pnl and
kind=equity are served from the nightly archive.
GET /v1/networkExchange population rather than prices: wallets in existence and how many were created in the last 24 hours, active perp traders, open positions, total equity with its spot/perp/staking/vault split, and the equity distribution by band. Every headline carries its own 24h change, and the rolling 24h creation figure is the sum of the hourly series returned beside it, so the number and the chart cannot disagree.
Fields that need an index of every address — which no public
endpoint provides — are null with the reason in
note and their names in unavailable, never
estimated.
GET /v1/udf/*
A TradingView UDF datafeed: /config, /symbols,
/search, /history, /time. The
dashboard chart uses TradingView's open-source Lightweight Charts, which
needs none of this — these exist so their full Advanced Charts
library can be dropped in without touching the back end.
static/udf-datafeed.js is the adapter. The key goes in the
X-API-Key header, never a query string: a datafeed URL ends
up in history, referrers and logs.
GET /v1/funding · /v1/stats · /v1/liquidation-riskLive funding, open interest and volume per pair; exchange-wide aggregates and leverage distribution; and the share of each cohort sitting within 25% of liquidation. That last one discriminates: proven-profitable cohorts consistently run less leverage, so convergence between cohorts is the thing to watch.
GET|POST|PATCH|DELETE /v1/watchlist
Track addresses and get them back with live equity, bias, unrealised
PnL, both cohort badges and liquidation proximity in one call.
PATCH /v1/watchlist/{addr} sets label and
pinned; pinned rows sort first. Both are your own
annotations on a public address, which is the only part of a watchlist
worth storing for you.
POST /v1/webhooks Build
Fired alerts POST to your endpoint, signed. Every delivery carries
X-HC-Timestamp and X-HC-Signature, an
HMAC-SHA256 of <timestamp>.<raw body> keyed by
the secret shown once at creation. The timestamp is inside the
signature, not merely alongside it, so an old delivery cannot be
replayed with a fresh one — reject anything older than ~5 minutes.
Ten consecutive failures disables an endpoint.
GET /v1/zones Pro
Support and resistance ranked by profit-cohort dollars resting there.
?coin=BTC&lookback_days=7. Each zone reports
cohort_usd, cohort_pct and a
conviction label, and the response includes a
coverage block stating how much of the book was read.
Add &segments= to choose which cohorts count — ids,
names like smart-money, or groups (profit,
loss, whales, retail, all).
elite_usd and elite_pct are kept as aliases so
existing clients keep working.
GET /v1/signal Pro
A weighted scorecard from −100 to +100 with every input broken out.
It is a summary of measured quantities, not a backtested predictor, and
the response says so in a caveat field.
&segments= chooses whose positioning is scored, using the
same ids, names and groups as /v1/zones. It defaults to
profit. The selection changes what the number
means, not how it is computed, so the response states its own
basis: segments, segment_names,
basis (profit, loss or
mixed) and a basis_note when the reading needs
inverting — +60 across the loss-making cohorts is the side that has
historically been wrong leaning long, which is not the same finding as
+60 across the profitable ones.
GET /v1/archive/history Desk
Daily cohort bias per coin for every archived day.
?coin=ETH. Also /v1/archive for depth and
/v1/archive/day?day=YYYY-MM-DD for one night. Requests
before the archive begins return before_archive_start
rather than an empty list — an empty result would read as "nothing
happened" when the truth is "we were not watching yet".
GET /v1/export Deskcurl -H "X-API-Key: $KEY" -o flow.csv \ "https://your-host/v1/export?dataset=flow&from=2026-08-01&to=2026-08-02"
Bulk download as csv or jsonl.
dataset=flow is the per-coin cohort aggregate over the
window (the same rows /v1/flow serves),
events is the raw position-change events behind it,
zones the current zone table for one coin, and
archive the nightly position snapshots. Freshness travels
in headers — X-HC-As-Of, X-HC-Age-S —
because a CSV has nowhere to put the envelope. Row counts are capped
per request; truncation is stated in X-HC-Truncated
and a trailing comment row, and every complete CSV ends with
# rows: N so a cut-off download is detectable.
GET /v1/stream Streamcurl -N -H "X-API-Key: $KEY" \ "https://your-host/v1/stream?coins=ETH&segments=profit"
The raw event firehose as Server-Sent Events: every cohort position
change as it lands, in the same shape /v1/flow consumes
— t, addr, seg,
size_seg, coin, d_szi,
szi, px, usd. Direction is the
sign of d_szi, measured from the position diff, never
inferred from trade-side ordering. Each event's SSE id is its
timestamp, so reconnecting with Last-Event-ID (or
?since=) replays the missed tail, bounded to ten minutes.
Comment heartbeats every 15s keep proxies from closing the pipe.
Concurrent streams per account are capped; the limit is in
X-HC-Stream-Limit on every response rather than
discovered by hitting it.
GET|POST|DELETE /v1/alerts Procurl -X POST -H "X-API-Key: $KEY" -H "Content-Type: application/json" \ -d '{"kind":"flow_bias","coin":"ETH","window_h":4, "threshold":0.7,"direction":"above"}' \ https://your-host/v1/alerts
Alerts are edge-triggered with hysteresis and are never evaluated against stale data — if the feed is behind, the pass is skipped rather than firing on a frozen number.
Kinds. Marketwide: flow_bias,
flow_usd, zone_touch, price,
large_position (any single cohort-wallet position
change over a dollar size, on any coin unless you name one — it
watches the classified cohorts the feed follows, not every wallet on
the exchange) and oi_move (percent
change in open interest since that alert last looked). Per wallet:
wallet_activity (open, close, add, reduce) and
wallet_transfer (deposits and withdrawals over an optional
floor) — both take addr instead of coin.
A wallet alert never fires on creation: the first pass records what the
wallet holds, and only a change after that is news.
Digests. Add interval_s — 300, 900,
3600, 21600 or 86400 — and the alert reports on that clock instead
of on a crossing. No hysteresis, because there is no edge to debounce.
Leave it out for the default behaviour, one message when it happens.
Limits. Each plan holds a fixed number of alerts:
Build 25, Desk 100, Stream uncapped. GET /v1/alerts returns
used, limit and remaining; going
over returns 402 naming the cap and the plans that hold
more.
channel is telegram (the default),
webhook or all. Telegram is bound per account
by a one-time code: the account requests a link, opens the bot with it,
and the bot reports which chat sent it — a chat id is never typed
into a form, so a typo cannot deliver your positions to a stranger.
Creating an alert with no chat connected still returns 201, with a
warning saying it has nowhere to go; finding that out when
it fails to arrive would be too late.
GET /v1/healthNo key required. Per-upstream freshness, measured from the newest fact in each feed. Publicly checkable on purpose.
Every plan gets every endpoint except webhooks, the archive and the firehose, and every plan gets every coin with no delay. What you buy is resolution.
| Plan | Price | Resolution | Requests/hour | Adds |
|---|---|---|---|---|
| Free | $0 | 900s | 250 | — |
| Build | $99 | 60s | 6,000 | alerts, webhooks |
| Desk | $349 | 20s | 30,000 | archive, export |
| Stream | $1200 | 20s | 250,000 | firehose |
Every response carries X-RateLimit-Limit,
X-RateLimit-Remaining and X-RateLimit-Reset, so
you can back off before being refused. 401 means a missing or
revoked key, 402 means your plan excludes the endpoint (the
body lists which plans include it), 429 means the hourly
limit with a Retry-After. Asking for finer resolution than
your plan allows is not an error — you get a 200 with the
coarser data and resolution_clamped set, because a chart
that renders beats a refusal.
Cohort membership is the set of wallets with a proven profit record,
split into three segments. Flow comes from diffing each wallet's actual
position between polls: d_szi > 0 means that wallet got
longer, and szi is the resulting position, so a wallet still
shows up only while it is still in the trade.
Direction is never inferred from which side of a trade a wallet appears on. That ordering is undocumented, and reading it wrong would invert every bias while still producing numbers that look entirely reasonable.