Vike Data API

Dados cripto para agentes de IA: carteiras e tokens on-chain, Hyperliquid, HL Cohorts e Polymarket.

Servidor MCP hospedado

npx add-mcp 'https://data.vike.io/mcp'

Instala no Claude Code, Codex, Cursor e outros

Documentação

LIVE --:--:-- UTC Hyperliquid positions snapshot on a 5-min UTC grid — last boundary --:-- next in -:--

  1. Sign in at vike.io Any account. No card, no plan to pick.
  2. Create a key On /account?tab=api-keys. Up to 3 active keys per account; every key starts vk_. You can read a key back later from the same page.
  3. Send the header X-API-KEY: vk_… on every request. OAuth clients: Authorization: Bearer vk_oat_…. Claude Desktop needs no key at all.

Reference · every section of this page

  1. Alerts API
  2. Alert feed
    1. The webhook body 2. Event shapes
  3. WebSocket
    1. Resuming 2. Filters 3. Close codes
  4. CLI
  5. MCP
    1. Clients 2. Alert tools 3. Search and streams 4. Candles 5. Perps
  6. Glossary
  7. Not covered
  8. License

Each product's own routes, channels and tools: On-chain, Hyperliquid, HL Cohorts, Polymarket, Predict.fun. What changed: Changelog.

Your first request

Choose how to connect, make a first call, download a first file.

You are buildingUseStart at
A script, a notebook, a backfillREST: JSON over HTTPS at https://data.vike.io/v1First request, then each product's routes
An AI client: Claude, ChatGPT, Cursor, Gemini CLI, CopilotMCP at https://data.vike.io/mcpClients
A terminal session or a coding agentthe vike-data CLICLI
Live events: fills, liquidations, transfers, tradesthe WebSocket at wss://stream.vike.ioWebSocket
A research dataset, or a backtest over whole daysthe Parquet archivesFirst archive file
curl -H "X-API-KEY: $VIKE_API_KEY" \
  "https://data.vike.io/v1/hyperliquid/ohlcv?symbol=BTC&interval=1h&limit=3"

Your first archive file

# the index needs no key: every date and family, row counts, byte sizes, the schema
curl -s https://data.vike.io/archive/manifest.json | jq '.dates[0]'

# a full day takes two requests: ask for the link with your key (one request) ...
URL=$(curl -s -H "X-API-KEY: $VIKE_API_KEY" \
  "https://data.vike.io/v1/polymarket/archive/url?date=2026-09-01&stream=trades" | jq -r '.url')
# ... then download it: the link is the credential, valid 7 days
curl -f -o trades-2026-09-01.parquet "$URL"
  • Neither download spends your hourly budget. Asking for a link is one ordinary request.
  • The Hyperliquid samples are positions-sample.parquet and fills-sample.parquet: BTC on one published day, every open position at its first snapshot and every fill of its first hour, rebuilt about once a week; hl-samples/manifest.json names the day.
  • Download whole files rather than reading them over HTTP: a Parquet reader pointed at a URL makes a Range request per column chunk (one 1.26 GB partition was measured at 20,237 requests). Limits has why.
  • The files themselves: Polymarket historical data and Hyperliquid historical data.

Two credentials, tried in this order on every surface: Authorization: Bearer vk_oat_… (an OAuth 2.1 access token) and then X-API-KEY: vk_… (an API key). A bearer that does not validate falls through to the key header, so a request may carry either or both; both reach the same account.

SurfaceCredential
https://data.vike.io/v1/…X-API-KEY or an OAuth bearer
https://data.vike.io/archive/manifest.json, samples/ and hl-samples/none: they are open
an archive file link (Cloudflare R2, valid 7 days)none: the link is the credential. The key goes on the call that asks for it, https://data.vike.io/v1/polymarket/archive/url or https://data.vike.io/v1/hyperliquid/archive/url
https://data.vike.io/mcpOAuth for desktop clients, X-API-KEY for everything else
wss://stream.vike.io/v1/stream/…either header on the handshake, or ?token=vk_… for a browser, which cannot set WebSocket headers (details)
  • Header names are case-insensitive: X-API-KEY and X-API-Key are the same header.
  • The credential always travels in a header. A key in a query string authenticates nothing on REST, the archive or MCP, and no cookie or vike.io session does either.
  • Up to 3 active keys per account; one more is refused with a 400. Deactivating or deleting a key frees its slot. Every key starts vk_: match on that prefix, never on the length.
  • Keys are recoverable. An encrypted copy is stored, so /account?tab=api-keys shows a key again later. Only keys made before that existed cannot be read back (the page answers 410): make a new one.
  • Every key has the same reach: there are no scopes and nothing to narrow.
  • An account whose e-mail address is not confirmed is refused with 403 and code EMAIL_UNCONFIRMED on /v1, the archive, /mcp and the stream. Open the link in our e-mail, or get a new one at vike.io/account; the same credential works again within 45 seconds.

OAuth 2.1

Claude Desktop, ChatGPT and other clients that support Dynamic Client Registration (RFC 7591) need no key: the client registers itself and you approve in a browser. The server is on data.vike.io, sign-in stays on vike.io: an unauthenticated call to /mcp is answered 401 with WWW-Authenticate: Bearer resource_metadata="https://data.vike.io/.well-known/oauth-protected-resource", and that document names https://vike.io as the authorization server.

FieldValue
Metadata (RFC 8414)https://vike.io/.well-known/oauth-authorization-server
RegistrationPOST https://vike.io/oauth/register
Authorization / token / revocation/oauth/authorize, /oauth/token, /oauth/revoke on https://vike.io
Scopemcp:tools only; anything else is invalid_scope
Grantsauthorization_code, refresh_token; response type code
PKCES256, mandatory
Lifetimescode 10 minutes, access token 1 hour, refresh token 30 days; a refresh rotates the token
Access tokensstart vk_oat_, sent as Authorization: Bearer
Redirect URIsclaude.ai and claude.com, loopback addresses (localhost, 127.0.0.1, [::1]), and exact vendor callbacks for ChatGPT, Cursor web, VS Code and Perplexity; any other is invalid_redirect_uri, and that client uses a key

Connected apps are listed, and revocable, under Account → API Keys → Connected Apps. Revoking one revokes every token that client holds for your account and leaves your other clients alone. All of an account's OAuth tokens share one hourly budget; each API key has its own. Tokens, codes and client secrets are stored only as SHA-256 hashes: a plaintext value is shown once, when it is issued.

Revocation takes seconds, not zero

  • A valid credential is trusted for up to 45 seconds before it is checked again, so a deleted key or a revoked token can keep working for that long.
  • An invalid credential is remembered for up to 8 seconds: after fixing a typo, give it a moment.
  • A check that fails for an infrastructure reason is never cached, so an outage cannot turn into a rejection of a valid key.

Keeping a key safe

  • Keep it in an environment variable or a secret store, never in source control or a URL.
  • Rotate by creating the new key first, moving traffic to it, then deleting the old one.
  • If a key leaks, delete it: it stops authenticating within 45 seconds.
# an API key
curl -H "X-API-KEY: $VIKE_API_KEY" "https://data.vike.io/v1/onchain/tokens/search?q=USDC&limit=3"

# an OAuth access token
curl -H "Authorization: Bearer $VIKE_OAUTH_TOKEN" "https://data.vike.io/v1/onchain/tokens/search?q=USDC&limit=3"

Limits

One free key: 20,000 requests an hour, 200 a minute.

Requests20,000 per hour, per key: a rolling hour, so capacity returns continuously
Per minute200 per minute, per key, on /v1 only: a sustained rate, with room for a short burst above it
KeysUp to 3 active per account. Each key is its own budget.
OAuth appsAll of an account's OAuth tokens share one pooled budget
Price$0: nothing is billed
Archive downloadsNot counted against the hourly limit. A file needs a key only to ask for its signed link (one ordinary request), and the download itself carries none
Archive linksValid 7 days, for any number of requests (range reads included). No download quota
Over the limit429 with Retry-After: 60 over the hourly limit, Retry-After: 1 over the per-minute cap
Failed requestsCount toward the hourly ceiling like any other: back off on a 429, do not retry in a tight loop
HeadersEvery /v1 response, the 429 included, carries X-RateLimit-Limit (the hourly budget) and X-RateLimit-Remaining. There is no X-RateLimit-Reset: a rolling hour has no reset instant
WebSocketConnecting, subscribing and the frames you receive are not counted
Keyless/archive/samples/ · /archive/hl-samples/ · /archive/manifest.json

One ceiling, shared. MCP tool calls and REST calls made with the same key draw on the same 20,000-an-hour counter, and the counter is per key: keys are how you keep a bulk job from starving an interactive client. The per-minute cap, 200 a minute per key, applies to /v1 requests only, never to MCP or archive downloads.

Why the archive is not counted. The ceiling counts HTTP requests, and requests are the wrong unit for bulk files: a Parquet reader pointed at a URL fetches the footer and then every column chunk as its own Range request, thousands for one daily partition. So you ask once for a file's link, one ordinary request, and what you do with the link is charged against nothing.

Download whole files anyway. curl -O pulls a partition once and curl -C - -O resumes an interrupted one; remote-reading re-fetches the footer and column chunks on every query.

A backstop, not a meter. The ceiling is enforced approximately: each server process keeps its own count and re-syncs it on a short interval, and a failed re-sync reuses the last count rather than opening the gate. Design to stay under it, not to spend it to the last request. Over the per-minute cap, wait a second and carry on, or pace a bulk job to under 200 requests a minute (about 3 a second).

Credits

A credit is an accounting figure written to the usage row of each call. Nothing compares it against a balance, so it never blocks a call. A call that fails (a 400, 429 or 503) logs 0 credits, though it still spends one unit of the hourly limit, which counts requests, not credits.

REST: 1 credit per call, except:

RouteCredits
GET /v1/alerts/channels0
POST /v1/alerts/channels/webhooks2
POST /v1/alerts/channels/{channel_id}/rotate-secret2
GET /v1/alerts/rules0
POST /v1/alerts/rules3
POST /v1/alerts/rules/{alert_id}/delete2
POST /v1/alerts/rules/{alert_id}/edit2
GET /v1/hyperliquid/ohlcv/export2

MCP: each tool has its own price, 0 to 5 credits; 6 tools at 0 credits; 50 tools at 1 credit; 20 tools at 2 credits; 5 tools at 3 credits; 1 tool at 4 credits; 1 tool at 5 credits. Each tool's price is on its card: On-chain, Hyperliquid, HL Cohorts, Polymarket, Predict.fun and, below, the shared tools.

Your usage

Recent calls per key, with each one's credits, status and latency, are under Account → Usage (vike.io/account?tab=usage). Rate-limited calls are recorded with 0 credits, so a client hammering past the ceiling shows up rather than looking as if it stopped.

Each call is listed under a label: mcp for a tool call, api_<namespace>_<resource> for a /v1 route (the first two path segments, api_onchain_wallets for /v1/onchain/wallets/…). An archive file link is not recorded; the request that asked for it is. The labels separate your usage, not your budget: mcp and /v1 draw on the same counter.

Errors

Branch on the status code, not the body: the body depends on which hop answered.

StatusMeaningBody
400The input was rejected: an unknown market or dex, a malformed identifier, a cursor from another query. Polymarket routes also refuse unknown query parameters.Hyperliquid / on-chain {"detail": "…"} · Polymarket {"error": "…"}
401Missing, invalid or deactivated credential, on /v1, the archive and /mcp alike.the UNAUTHORIZED envelope below (MCP: a JSON-RPC error)
403A valid credential of an account whose e-mail address is not confirmed (EMAIL_UNCONFIRMED), or a Hyperliquid download link past its 7 days or altered (answered by Cloudflare R2, an XML body).the envelope with code EMAIL_UNCONFIRMED
404No such route or archive file. An unknown condition_id on a Polymarket detail route is 200 [], not a 404. A path people often guess carries a hint (below).{"detail": "Not Found"}
414The request line is longer than 16 KB: split a long id list (/v1/polymarket/events takes at most 100 token_ids).the envelope with code URI_TOO_LONG
422A parameter failed type or range checking (limit above its maximum, a word where a number goes). Polymarket reads its parameters as text and answers 400 instead.{"detail": [ … ], "error": "…"}
429The hourly limit, or on /v1 the per-minute cap (200 a minute per key).Retry-After: 60 (hourly) or Retry-After: 1 (per-minute), the RATE_LIMITED envelope
503The data store failed. Not your request: retry with back-off.Hyperliquid / on-chain {"detail": "upstream unavailable"} · Polymarket {"error": "upstream unavailable"}
{
 "error": {
  "code": "UNAUTHORIZED",
  "message": "invalid or missing credential",
  "docs": "https://vike.io/data/api"
 }
}
  • A failed query is never a 200 with an empty body. An empty list means there were no rows; a store outage is a 503.
  • Every refusal our servers produce carries a JSON body, the 401 and the 414 included. A hop in front of us can still answer with its own HTML page (a Cloudflare 5xx), so branch on the status code, not the body.
  • A 429 on a route usually called in a loop carries a hint naming the bulk call that replaces the loop, for example on /v1/polymarket/settlements/{condition_id}. MCP's 429 carries none.
  • The /v1/onchain routes and Hyperliquid /traders and /positions/match run through a shared tool layer that reports its own failures in-band, so an internal error there can arrive as a 400 carrying the error text.
  • MCP answers a bad credential with HTTP 401, a WWW-Authenticate: Bearer resource_metadata=… header and a JSON-RPC error (-32000, Invalid or missing API key); a rate-limit trip there is a real 429. A failed tool call is an HTTP 200 whose result carries isError: true: check the body, not just the status (MCP).

A 404 on a path you expected

The paths people guess most often, and the call that answers the same question. A 404 on one of them carries the answer in a hint field; detail is unchanged.

You triedUse instead
/v1/hyperliquid/wallets/{address}/positions/v1/hyperliquid/positions?wallet={address}; add start and end (ISO-8601 UTC) for history, symbol to narrow it
/v1/hyperliquid/candles/v1/hyperliquid/ohlcv?symbol=BTC&interval=1h
/v1/hl-cohorts/cohort-metrics/v1/hl-cohorts/symbols/BTC/cohort-metrics: the symbol is in the path
/v1/hl-cohorts/positionsonly the cohort routes live under /v1/hl-cohorts; positions, leaderboards, wallets, liquidations, builders and the archive are under /v1/hyperliquid
/v1/polymarket/settlements/v1/polymarket/settlements/bulk for many markets, /v1/polymarket/settlements/{condition_id} for one
/v1/marketdata/fundingnot part of the API: centralised-exchange data is not offered

A 429 names the bulk call

Routehint
/v1/polymarket/settlements/{condition_id}Many markets? GET /v1/polymarket/settlements/bulk returns up to 5,000 settlements per request (filter with slug_prefix, from_ms, to_ms; page with cursor).
/v1/polymarket/markets/{condition_id}Many markets? GET /v1/polymarket/markets returns up to 100 per request (query= filters by slug).
/v1/polymarket/eventsFor bulk history download the daily Parquet files: GET /v1/polymarket/archive/datasets lists them, GET /v1/polymarket/archive/url returns one; downloads do not count against the request limits.
/v1/polymarket/market_statsFor bulk history download the daily Parquet files: GET /v1/polymarket/archive/datasets lists them, GET /v1/polymarket/archive/url returns one; downloads do not count against the request limits.
/v1/polymarket/ohlcvFor bulk history download the daily Parquet files: GET /v1/polymarket/archive/datasets lists them, GET /v1/polymarket/archive/url returns one; downloads do not count against the request limits.
/v1/polymarket/orderbookFor bulk history download the daily Parquet files: GET /v1/polymarket/archive/datasets lists them, GET /v1/polymarket/archive/url returns one; downloads do not count against the request limits.
/v1/hyperliquid/ohlcvMany symbols? GET /v1/hyperliquid/ohlcv/latest returns the newest 1-minute candle of every symbol that traded in the last hour, in one request.
/v1/hyperliquid/positionsFor bulk history download the daily Parquet files: GET /v1/hyperliquid/archive/url?dataset=positions&date=YYYY-MM-DD returns a link (index: GET /v1/hyperliquid/archive/datasets); downloads do not count against the request limits.
/v1/hyperliquid/wallets/{address}/fillsFor bulk history download the daily Parquet files: GET /v1/hyperliquid/archive/url?dataset=fills&date=YYYY-MM-DD returns a link (index: GET /v1/hyperliquid/archive/datasets); downloads do not count against the request limits.

Advisory headers

Some answers carry advice in a header; the body never changes, so two identical requests still return identical bytes.

HeaderWhen
Deprecation: true and Link: <…>; rel="successor-version"A /v1/hyperliquid request used a retired spelling of a parameter or a path. The Link is the same request in the current spelling; the answer itself is unchanged.
X-Vike-NoteAn empty answer from /v1/polymarket whose reason the request shows: a window that ends before 2026-07-25, when order-book recording starts (/ohlcv, /events, /orderbook); from after to on /ohlcv; a condition_id that is not 0x and 64 hex digits.
X-Vike-HintAbout the hundredth call in a minute from one key to /v1/polymarket/settlements/{condition_id} or /v1/polymarket/markets/{condition_id}: names the bulk route, as the 429 hint does.

Pagination

Four ways a route pages, and the on-chain routes that do not.

Hyperliquid and HL Cohorts page by keyset, never by offset. A list response carries nextCursor; pass it back verbatim as cursor until it is null or a page comes back empty. On most list routes limit defaults to 100 and goes to 5,000; each route's card states its own bounds. Keyset paging costs the same on every page and keeps rows from shifting between pages when new ones arrive.

import os, requests

H = {"X-API-KEY": os.environ["VIKE_API_KEY"]}
url = "https://data.vike.io/v1/hyperliquid/positions"

cursor = None
while True:
    params = {"symbol": "BTC", "limit": 5000} | ({"cursor": cursor} if cursor else {})
    page = requests.get(url, params=params, headers=H, timeout=60).json()
    rows = page["positions"]          # the list key differs per route
    if not rows:
        break
    handle(rows)
    cursor = page.get("nextCursor")   # null on the last page
    if cursor is None:
        break

Try the pager on a real captured answer: Hyperliquid AI & API → Paging.

There is no single convention across the namespaces, and a client written against one silently stops short against another:

StyleExample routeMove to the next pageStop when
Cursor/v1/hyperliquid/liquidationspass nextCursor back as cursor, every other parameter unchangednextCursor is null, or a page comes back empty
Cursor in an envelope/v1/polymarket/settlements/bulkpass next_cursor back as cursor, every other parameter unchangedhas_more is false
Offset/v1/polymarket/archive/datasetsadd the rows received to offsetoffset + len(datasets) >= total
Time window/v1/hyperliquid/ohlcvrestart start one bar after the last candle you holda page shorter than limit

How each namespace pages

NamespaceHow it pagesPass back
/v1/hyperliquid and /v1/hl-cohorts list routesKeyset. The response carries nextCursor (a string, or null), opaque and bound to the query that issued it.cursor=<nextCursor>, verbatim
/v1/hyperliquid/ohlcvNo cursor: one page per request; move start / end.—
/v1/polymarket/events{"events": [...], "has_more": …, "next_cursor": …}, at most 24 hours per call.cursor=<next_cursor> while has_more is true
/v1/polymarket/settlements/bulk{"settlements": [...], "has_more": …, "next_cursor": …}, oldest resolution first, up to 5,000 a page.cursor=<next_cursor> while has_more is true
Other /v1/polymarket routesA bare JSON array, except /archive/datasets ({"datasets": [...], "limit", "offset", "total"}, limit 200 by default, at most 1,000) and /leaderboard (offset up to 1,000); /wallets/{address} and /archive/url return one object.offset
/v1/onchainDoes not page: limit only, one page (below).—

Details that decide whether the walk is complete

  • A full last page still carries a cursor. nextCursor is set whenever a page holds limit rows, so a result whose size is an exact multiple ends on an empty page. Stop on either.
  • A cursor is bound to the query that issued it. Reuse it after changing start / end or on another route and you get a 400 telling you to restart without one (nextCursor belongs to a different query mode, or nextCursor was issued for different query parameters on the feeds and orders); a malformed cursor is a 400 too, never a silent reset to page 1. Send the same filters with every page.
  • The envelope says when to stop. next_cursor is a string only while has_more is true; the cursor is a position and the walk resumes strictly after it.
  • total is the filtered count before paging on the archive index, and the only field that says when to stop there.
  • Not every list route pages. /v1/hyperliquid/traders stops at 100 rows and /v1/polymarket/markets at 100: narrow the filter instead.
  • A board rebuilt hourly (/v1/polymarket/leaderboard) can repeat or skip a wallet across a rebuild: fetch the pages of one walk close together.

Following a feed

The Hyperliquid event feeds, /twaps, /wallets/{address}/twaps, /ledger and /leverage-changes, page by cursor and add a mode for reading forward as events arrive.

  • order=desc is the default, newest first, with the cursor rules above (/ledger drops duplicate rows from a page, so stop there on an empty page).
  • order=asc runs oldest first and is the polling mode: keep the last nextCursor and send it back, every other parameter unchanged. Once you hold a cursor it is never null in this mode: stop a bounded walk at the first empty page; a poller keeps the cursor and asks again later. An empty page returns the cursor you sent.
  • Without a wallet (or, on /twaps, a twap_id), a call with no start covers the 24 hours before end. A window of more than 31 days is a 400, an old poller's cursor included: restart it with start.
  • A forward poll can miss a late row inserted with an older key (the ledger's newest two hours can still receive one). If you must not miss one, re-read an overlap with start and drop what you hold by the full key: ts, hash, wallet on the ledger; ts, twap_id, status on the TWAP feeds; ts, wallet, symbol on the leverage feed.
  • A feed trails the venue by a minute or more (Freshness): an empty poll is "nothing new yet".

On-chain: one page and a silent cap

  • No /v1/onchain route takes a cursor or an offset. Each returns one page, and nothing in the answer says it was cut.
  • limit is checked against one ceiling and applied under another: /wallets/{address}/transactions accepts up to 1000 and returns at most 500, /wallets/{address}/counterparties accepts up to 1000 and returns at most 50. Each parameter list names both.
  • A full page at the cap is not the whole answer. Narrow the question instead: a shorter window or fewer days, a higher min_usd, a direction or one token_address, in several smaller calls.

Every dataset has its own request builder, fields and loading code on its product's pages: On-chain, Hyperliquid, HL Cohorts and Polymarket.

Conventions

Times, windows, identifiers and the two Polymarket differences.

WhereFieldFormat
Hyperliquid rows: positions, fills, cohort metrics, liquidationsts, snapshot_ts, liq_tsISO-8601 string with offset, 2026-10-12T09:00:00+00:00
Hyperliquid /ohlcvts (bar open)epoch milliseconds
Hyperliquid /symbols/hourlytsepoch seconds
Polymarket: API and archivets, local_ts, as_of_ts, first_ms, last_msepoch milliseconds. ts is the venue's event time, local_ts the recorder's receive time
Polymarket trader routesrow timesYYYY-MM-DD HH:MM:SS UTC strings

Every time is UTC. There is no API-wide window rule: each route's parameter list is the authority.

  • /v1/hyperliquid and /v1/hl-cohorts take start and end as ISO-8601 UTC where a route takes a range, most keeping a row stamped exactly at end. /ohlcv, /ohlcv/export and /symbols/hourly are half-open (end exclusive, so consecutive windows tile) and also accept epoch time. Other routes take a named window (traders, leaderboards, builders) or a number of hours (cohort series).
  • /v1/onchain takes no timestamps: a named window (window, period) or a number of days back from now.
  • /v1/polymarket spells it per route: /ohlcv takes from / to in epoch milliseconds with an inclusive to; /events and /settlements/bulk take from_ms / to_ms, half-open; the archive index takes from / to as dates, both inclusive.
IdentifierMeans
symbolA Hyperliquid instrument: BTC, a HIP-3 market xyz:TSLA, a spot pair @107. Until 2026-11-19 responses also carry its old name (asset or coin)
wallet · addressHyperliquid and EVM wallets, 0x and 40 hex characters
cohortA Hyperliquid cohort rung on the chosen axis; paths take its slug
token_idPolymarket: one outcome token (a decimal string; keep it a string)
condition_idPolymarket: the market, 0x -prefixed. Joins the API to the archive

There is no API-wide identifier: hyperliquid and hl-cohorts take a symbol, onchain an address, polymarket a token_id on its book and tape routes and a condition_id on its market and settlement routes. A response echoes the identity it resolved.

Two /v1/polymarket differences. Its 400 and 503 bodies are {"error": "<message>"} where the other namespaces answer {"detail": …}, and it reads its query parameters as text: an unknown parameter is a 400 naming the one you probably meant, never the framework's 422.

Every gated response carries Cache-Control: private, no-store, and every /v1 response a Link header to the spec (rel="service-desc").

Data-quality notes

  • Two identical requests return identical bytes. Aggregates are snapped to a fixed precision before they leave the server, so a re-run of a query cannot drift in its last digits, and a response can be cached, diffed or content-addressed.
  • Cohort labels declare their basis. Graded Hyperliquid responses say whether a label is point_in_time (as it was that hour) or current (today's): label_basis on the envelope or pnl_cohort_basis on the row.
  • Polymarket recording gaps have markers, and `seq` is not one. A lost socket shows as status rows: gap_start, then live_resume when the socket answers again, which does not prove rows have resumed: the token's next row does. stale means no fresh data while the socket stayed up. A recorder restart writes no status row: seq starts again at 1 on the token's next book. Otherwise seq counts the frames the recorder applied, so it runs on across a hole, and a jump means only rows dropped after they arrived. manifest.json lists every partition with its byte size and, where recorded, its row count.
  • Feed delay is measurable per event. Polymarket rows carry both the venue's ts and the recorder's local_ts.
  • A failure is never an empty 200, and an unknown Polymarket id is an empty array, not a 404. Both under Errors.

Freshness and completeness

How current each answer is, and how an answer marks what it leaves out.

Data reaches the API a little behind the clock, and some answers carry an old value in the shape of a current one. Each item names the field or the rule that tells the two apart.

On-chain

  • Balance amounts are live: /wallets/{address}/balances reads each amount from the chain at request time; which tokens it checks comes from our copy of the wallet's transfers, so a token first received after the last sync is not listed yet.
  • Transfers trail the chain by minutes: a sync extends our copy of each chain every few minutes, and a transfer's USD value arrives when pricing runs, so min_usd above 0 can skip the newest, still unpriced hours.
  • Leaderboard windows are rebuilt once a day: the 24h, 7d, 30d and 90d figures of /wallets/discover can be up to about a day old. On base and hyperevm a window other than all whose data is over 2 days old is a 400, not an old answer.

Hyperliquid

  • Position matching has a 15-minute gate: /positions/match matches only positions seen within 15 minutes of the newest snapshot batch, so it returns fewer rows than the position history holds.
  • The daily files appear in /archive/datasets once the day is complete, early the next UTC morning; the latest positions file is replaced about every 5 minutes, and latest.positions carries its snapshot_ts and generated_at.
  • Event feeds trail the venue: TWAP statuses within a minute or two, ledger events about a minute (the newest two hours can still gain a late event; each hour is re-read about two hours after it ends), leverage changes a few minutes to about twenty after the change. A row's ts is when the event happened.

Orders: the snapshot is the clock

/orders, /wallets/{address}/orders and /symbols/{symbol}/order-clusters answer from a snapshot, not from the live book. snapshotTs (snapshot_ts on the clusters route) names the instant an answer describes, and source names the feed that answered (on the wallet route, the feed behind the newest snapshot, not the window): l4_5m, the 5-minute snapshots from 2026-10-23 21:45 UTC, or state_12m, the checkpoints taken about every 12 minutes from 2026-07-29. Read snapshotTs for the age of an answer rather than assume it. On l4_5m the instant is a snapshot on the 5-minute grid and the answer holds every order seen in it, except for an at inside an outage of that feed (below).

For a past at answered from checkpoints, snapshotTs is your at, not a checkpoint. For a past at answered from the checkpoints (before 2026-10-23 21:45 UTC, or after a stalled 5-minute feed's last snapshot), snapshotTs is the at floored to 5 minutes, not a checkpoint instant. Checkpoints are about 12 minutes apart and off the 5-minute grid, so an order is listed only if a checkpoint at or before that instant saw it and another at or after it saw it again. An order placed after the earlier checkpoint, or gone before the later one, is missing although it was resting at snapshotTs. An at inside an outage of either feed is answered the same way, from the sightings on both sides of the gap, with a normal-looking source. Without at, a state_12m answer is the newest checkpoint and snapshotTs is its instant; /wallets/{address}/orders has no at and names the newest snapshot, or the snapshot of page 1 on a cursor page.

If the 5-minute feed stops, a call without at is answered from the 12-minute checkpoints. When the newest 5-minute snapshot is more than 15 minutes behind the newest checkpoint, a call without at, and an at after that feed's last snapshot, are answered from the checkpoints and source says state_12m, instead of serving a stale 5-minute snapshot in the shape of a current one.

HL Cohorts

  • The newest row of /symbols/{symbol}/metrics is provisional: provisional: true on at most one row, the hour in progress; its ts moves on every poll, so drop it before storing a series.
  • /symbols/{symbol}/cohort-metrics carries freshness on a window that reaches the present: newest_ts, behind_hours, settled_lag_ceiling_hours and stale, the pre-computed verdict. Read the age there, never from prose.
  • grading=realized-pit is rebuilt once a day, just after 00:00 UTC, through 23:00 of the last complete UTC day, so its newest hour is 1 to 25 hours old; freshness.stale says so. For live use take grading=realized with labelBasis=point_in_time, one hour behind the clock. A window ending in the past carries no freshness block.

Polymarket

  • A book snapshot is as old as as_of_ts on /orderbook, not the ts you asked for.
  • The recorded book starts on 2026-07-25: /ohlcv, /events and /orderbook answer empty before it, with an X-Vike-Note header saying why.
  • The leaderboard is rebuilt hourly; its 24h, 7d and 30d windows can be revised by a later rebuild.
  • Bulk settlements are rebuilt every 5 minutes: a market that has just resolved can take up to about 5 minutes to appear in /settlements/bulk; /settlements/{condition_id} is always current.
  • The archive: read generated_at in manifest.json before trusting a coverage figure.

What an answer can leave out

  • On-chain routes return one page, capped (On-chain: one page).
  • On-chain history starts on a different date per chain; /wallets/{address}/first-funder says where in coverage.
  • /labels/{address}/provenance returns fewer rows while a label source is being refreshed.
  • Each Hyperliquid positions day in the archive index says how many 5-minute snapshots it holds, and reconstructed_hours lists the hours rebuilt from fills.
  • The live channels run on demand, and a window nobody subscribed to is a gap frame, never filled in (WebSocket).

OpenAPI: the spec, Swagger UI and ReDoc

The spec describes every route's parameters; response fields are on each route's card.

The spec is public at vike.io/data/openapi.json: no key, not metered, the same document the API serves plus a top-level x-vike-limits block. Every /v1 response names it in a Link header (rel="service-desc"), so a client holding only a response can find it.

SurfaceURLKey
openapi.json, the keyless copyhttps://vike.io/data/openapi.jsonno
openapi.json on the API hosthttps://data.vike.io/v1/openapi.jsonyes
Swagger UIhttps://data.vike.io/v1/docsyes
ReDochttps://data.vike.io/v1/redocyes

The three on the API host sit under /v1/ and behind the same header-only gate as the operations they describe (the bare /openapi.json, /docs and /redoc answer 404), and each request there is an ordinary metered call.

openapi.json

What the spec declares, counted from the published document:

FieldValue
OpenAPI version3.1.0
info.title / info.versionvike.io Data API / 1.0.0
servershttps://data.vike.io
Operations121: 113 GET, 8 POST
Parameters520, 520 with a description
components.schemasHTTPValidationError, ValidationError
Response codes declared200 on every operation, 422 on 113
securitySchemesOAuth2Bearer, ApiKeyHeader
x-vike-limitshourly_limit 20,000, minute_limit 200, credits_per_call 1 and the credit_overrides

Parameters are the part it carries in full: name, location, type, whether required and a description, the same text each route card on these pages shows. The auth block is real: both ApiKeyHeader (X-API-KEY) and OAuth2Bearer, as a list of single-key requirements, which in OpenAPI means either, not both.

You cannot generate a full client from it. It declares no response schemas (every 200 is an undeclared object; the two components.schemas are validation errors), only 200 and 422 among the status codes the API really returns (Errors), and nothing of the archive or the WebSockets, which OpenAPI has no operation for. Response fields are on each route's card, read from real responses.

curl -s https://vike.io/data/openapi.json -o openapi.json

# what it declares, in one jq
jq '{paths: (.paths | length), params: [.paths[][].parameters // [] | .[]] | length, schemas: (.components.schemas | keys)}' openapi.json

Swagger UI

Swagger UI at https://data.vike.io/v1/docs: the interactive, one-operation-at-a-time view of the same document. It is an HTML page behind the header-only gate, so a browser cannot open it by itself: no cookie, session or ?token= authenticates data.vike.io. Reach it with a header-injecting extension or proxy, or work from the keyless file. The page then fetches /v1/openapi.json itself, a second request behind the same gate, so the injected header must cover that fetch too; its Authorize dialog attaches your key to Try it out calls.

ReDoc

ReDoc at https://data.vike.io/v1/redoc: the same document as one scrollable reference page, behind the same gate as Swagger UI.

Search across every product: GET /v1/search

The vike.io search box as one route: a name, symbol, address, ENS name or pasted link in, every product's matches out.

Search · REST · CLI · MCP

Search by name, symbol or address

Route

GET /v1/search

CLI

vike-data search

MCP

search · 1 credit

Cost

1 credit, one request of the hourly limit

Free API Archive Stream MCP

Tokens, Hyperliquid markets, traders, vaults and builders, labelled wallets and entities on four chains, Polymarket wallets and markets, Predict.fun markets and traders, and did_you_mean, for one query: the vike.io search box, across every product.

{"tokens", "hl_markets", "wallets", "traders", "vaults", "builders", "polymarket", "markets", "predictfun", "did_you_mean"}, each a list of at most limit rows. A token's price_usd and chg_24h_pct are Hyperliquid's, or null for a token not traded there; a symbol listed only on a centralised exchange is not offered (unlike the website's box). A token's chain (ethereum, bsc, base, hyperevm, or another chain it is issued on) and token_address (its contract on that chain, an EVM one as lowercase hex without 0x; null for a coin with no contract) say which token it is. A wallets row carries ens, the address's ENS name, when it has one, and an entity row confidence, "verified" or "predicted", the best label confidence among its wallets; each key is absent when unknown. A wallets row of a labelled entity (entity) has wallet_count and url, the vike.io page listing its wallets; it has an address only when the entity is one wallet (a group has no single address). Several Polymarket markets of one event come back as one markets row with event_slug, title and markets_n. A predictfun row is a Predict.fun market (market_id, title, question, status: open, closed or resolved; open ones, the busiest (24 h volume) first, then the newest, and the others only when nothing else matched) or, for a pasted address that has traded there, that trader (address). A section whose read fails is an empty list (the website's behaviour), so an empty section is not proof of "no match". url fields are vike.io paths. Unlike the website, no Polymarket trader is found by X handle and no handle is returned (Polymarket's terms). One credit.

hl_markets holds Hyperliquid builder markets such as xyz:GOLD; polymarket holds Polymarket wallets by name, pseudonym or address, with the owner of an account or the accounts a personal wallet owns; markets holds open Polymarket markets with every word of q, and closed ones only when nothing else matched; predictfun holds open Predict.fun markets with every word of q (closed and resolved ones only when nothing else matched) and the Predict.fun trader a pasted address names; did_you_mean holds coins one typo away when no token matched. An ENS name is searched as the address it points to, a polymarket.com link as its market, and a type word such as cex, bridge or hacker lists that type's biggest entities.

Parameters

ParameterInDescription
q requiredqueryWhat to look for, 1 to 256 characters: a name, symbol, label, address, ENS name or polymarket.com link. An address inside a pasted URL is found. Filter words may go with it or alone: chain:base (eth, bsc, base, hyperevm), type:cex (an entity type), pnl>1m or pnl<-100k (traders' all-time PnL; k, m, b); the answer's filters lists the ones understood
limitqueryRows per section, 1 to 20. Default 5

Call it

curl -H "X-API-KEY: $VIKE_API_KEY" \
  "https://data.vike.io/v1/search?q=wintermute&limit=2"

A real answer

A real answer: GET /v1/search?q=wintermute&limit=2, captured 2026-11-04 11:03 UTC.

{
 "tokens": [],
 "hl_markets": [],
 "wallets": [
  {
   "chain": "ethereum",
   "label": "Wintermute",
   "sub_label": "2 chains",
   "is_flagged": false,
   "type": "",
   "entity": "wintermute",
   "wallet_count": 77,
   "url": "/onchain/entity/wintermute"
  },
  {
   "chain": "ethereum",
   "label": "Wintermute Deposit",
   "sub_label": "market_maker · 43 wallets · 4 chains",
   "is_flagged": false,
   "type": "market_maker",
   "entity": "wintermute deposit",
   "wallet_count": 43,
   "url": "/onchain/entity/wintermute%20deposit",
   "confidence": "verified"
  }
 ],
 "traders": [],
 "vaults": [],
 "builders": [],
 "polymarket": [
  {
   "address": "0x1050854d71bde435fb32b0cd6704e7ea7e0c43af",
   "name": "Wintermute-Sport",
   "pseudonym": "Sharp-Preservation",
   "volume_usd": 454942.476049,
   "badges": [
    "🐬 Dolphin"
   ],
   "pnl_usd": -17260.08753
  },
  {
   "address": "0xf239d3d01e1e9a1d17f455f27feffbdaa8c13627",
   "name": "wintermute",
   "pseudonym": "",
   "volume_usd": 7423.453015000001,
   "badges": [],
   "pnl_usd": -1374.500349
  }
 ],
 "markets": [],
 "did_you_mean": []
}

10 routes under /v1/alerts

Your own alert rules, channels, webhook secrets and the events your rules produced: the REST face of the alert tools.

The 10 routes under /v1/alerts are the REST face of the alert tools: each runs the same code as its MCP tool, takes the same input and costs the same credits. They read and write the account your key belongs to, not one of the data products: another user's rule or channel is a 404, the same answer as one that does not exist. The same rules can be made under Account → Alerts.

  • A POST body is the tool's input, field for field (the rule id of an edit is in the path). A field the tool does not take, or a value of the wrong JSON type, is a 400 naming the field; every other route takes no body.
  • 400 the rule or body was refused, in the tool's words (No token BTC on eth: an alert on it could never fire. Did you mean WBTC?); 401 no valid credential; 404 not yours or does not exist; 429 the hourly budget or the per-minute cap; 503 nothing was stored, ask again. A call answered with an error costs nothing.
  • A generated webhook secret is returned once, in the answer, with Cache-Control: no-store, and shown again under Account → Alerts → channels (Show secret). The body of a call is not recorded in your usage history, only its query string.
  • At most 200 active alerts per account, of any kinds.

GET /v1/alerts/rules Your alert rules · alerts_list

POST /v1/alerts/rules Create an alert rule · alerts_create

POST /v1/alerts/rules/{alert_id}/edit Change an alert rule · alerts_edit

POST /v1/alerts/rules/{alert_id}/delete Delete an alert rule · alerts_delete

GET /v1/alerts/channels Your notification channels · alerts_list_channels

POST /v1/alerts/channels/webhooks Register a webhook channel · alerts_register_webhook

POST /v1/alerts/channels/{channel_id}/rotate-secret Rotate a webhook's signing secret · alerts_rotate_webhook_secret

POST /v1/alerts/channels/{channel_id}/test Send a webhook the test event again · alerts_test_webhook

GET /v1/alerts/events Your alert events, oldest first · alerts_events

POST /v1/alerts/deliveries/{delivery_id}/resend Send one of your deliveries again · alerts_resend_delivery

Alerts · REST · CLI · MCP

Your alert rules

Route

GET /v1/alerts/rules

CLI

vike-data alerts list

MCP

alerts_list · 0 credits

Cost

0 credits, one request of the hourly limit

Free API Archive Stream MCP

Your alert rules, newest first: {"alerts": [...]}, each with id, label, alert_type, chain, token_symbol, min_value_usd, max_value_usd, min_amount, address_conditions, cooldown_minutes, notify_channel_ids, is_active, created_at, policy (how often it notifies: per_event, cooldown or hysteresis) and, for a Hyperliquid rule, hl (its parameters). Free (0 credits), like the alerts_list MCP tool.

Parameters

ParameterInDescription
include_inactivequeryAlso list paused rules. Default false.

Call it

curl -H "X-API-KEY: $VIKE_API_KEY" \
  "https://data.vike.io/v1/alerts/rules?include_inactive=true"

The answer

Its shape, with illustrative values: these routes read and write your own account, so nothing here is a capture.

{
 "alerts": [
  {
   "id": 77,
   "label": "Big USDC moves",
   "alert_type": "transfer",
   "chain": "eth",
   "token_symbol": "USDC",
   "min_value_usd": 1000000.0,
   "max_value_usd": null,
   "min_amount": null,
   "address_conditions": [],
   "cooldown_minutes": 60,
   "max_triggers_day": 10,
   "notify_channel_ids": [
    12
   ],
   "is_active": true,
   "created_at": "2026-11-04T09:00:00+00:00",
   "policy": "cooldown",
   "hl": null,
   "pm": null
  }
 ]
}

Alerts · REST · CLI · MCP

Create an alert rule

Route

POST /v1/alerts/rules

CLI

vike-data alerts create

MCP

alerts_create · 3 credits

Cost

3 credits, one request of the hourly limit

Free API Archive Stream MCP

Create an alert rule. The JSON body is the input of the alerts_create MCP tool (same fields, same rules): an on-chain transfer alert (chain, token_symbol, min_value_usd, max_value_usd, min_amount in whole tokens, address, address_role) or a Hyperliquid alert chosen by alert_type; label is required. A token the chain does not have is refused with the near matches it does have (BTC on eth: did you mean WBTC?). Returns {"alert": {...}}, the new rule as GET /v1/alerts/rules lists it. 400 for a field the rule type does not take or a value it refuses; 503 when the token could not be checked (nothing is stored: retry). 3 credits, none on an error.

Parameters

ParameterInDescription
label requiredbodyName for this alert (e.g. 'Big USDC moves')
alert_typebodytransfer (default), a Hyperliquid type (hl_wallet, hl_cohort_fill, hl_liquidation, hl_cohort_bias, hl_liq_proximity, hl_wallet_orders) or a Polymarket type (pm_wallet_trade, pm_market_trade, pm_price_move, pm_resolution). Fixed once created.
token_symbolbodyTransfer: token ticker in UPPERCASE (e.g. USDC, ETH, PEPE). Hyperliquid: the perp coin as Hyperliquid names it (BTC, kPEPE, xyz:CL); required for hl_cohort_bias, optional for the rest (omit = every coin).
chainbodyTransfer alerts only: the chain to watch (single value). Default: eth. Ask the user to create two alerts if they want both chains. Leave out for Hyperliquid alerts.
min_value_usdbodyTransfer: alert when transfer USD value >= this. Hyperliquid: a floor on the fill or liquidation size in USD (hl_wallet, hl_liquidation); required and >= 10000 for hl_cohort_fill; the order's size x price for hl_wallet_orders (>= 1000, default 10000); not used by hl_cohort_bias or hl_liq_proximity. Polymarket: a floor on the trade's USD (size x price) for pm_wallet_trade; required and >= 1000 for pm_market_trade.
max_value_usdbodyTransfer alerts only: an upper bound on the transfer's USD value. A transfer worth more than this does not notify (one worth exactly this does). Like min_value_usd it needs a transfer that already carries a USD value, so an unpriced transfer never matches a rule with a bound. Keep it at or above min_value_usd: a rule whose minimum is above its maximum never fires.
min_amountbodyTransfer alerts only, with token_symbol: alert when the transfer moves at least this many WHOLE tokens (1000000 USDT, 0.5 ETH), whether or not it carries a USD value. With min_value_usd or max_value_usd as well, both must hold. A token whose decimals are unknown never matches it.
addressbodyTransfer alerts only: wallet to watch, one 0x + 40-hex EVM address. Matches transfers (native or token) the address sent or received on chain, per address_role.
address_rolebodyTransfer alerts only: any = sent or received (default), from = only transfers the address sent, to = only transfers it received. Needs address.
walletbodyOne 0x + 40-hex address. Hyperliquid: the account to watch, required for hl_wallet, hl_wallet_orders and hl_liq_proximity, optional for hl_liquidation. Polymarket: the proxy wallet (the address of its vike.io/polymarket/wallet page and of polymarket_tape rows), required for pm_wallet_trade.
condition_idbodyPolymarket only: the market, its 0x + 64-hex condition id. Required for pm_market_trade, pm_price_move and pm_resolution; optional for pm_wallet_trade (one market of that wallet).
actionsbodyhl_wallet only: which fills notify. Default: open, close, flip, increase ('add' is accepted for increase); reduce is opt-in.
sidebodyhl_wallet_orders only: only buy or only sell orders. Leave out for both.
eventsbodyhl_wallet_orders only: placed = a new order appeared in the book, gone = an order left it (its outcome filled, partially_filled or canceled comes from the order's own fills). Default both. Each check's orders arrive together, as one event.
pnl_cohortsbodyhl_cohort_fill and hl_liquidation: only wallets in these realised-PnL cohorts (the ladder runs from '3x smart' = top 1% to '3x rekt' = bottom 5%). hl_cohort_fill REQUIRES at least one PnL or size cohort; for hl_liquidation empty = any cohort.
size_cohortsbodyhl_cohort_fill and hl_liquidation: only wallets in these account-size cohorts (the ladder runs from '4x whale' = $10M+ to 'shrimp' = under $1k). hl_cohort_fill REQUIRES at least one PnL or size cohort; for hl_liquidation empty = any size.
indexbodyhl_cohort_bias only, required: the index watched for token_symbol. smi = long share of smart wallets minus long share of rekt wallets; wsi = long share of whales minus long share of shrimp; smart_bias / whale_bias = (long - short) / (long + short) of that cohort; smi_wsi_gap = smi - wsi. Ratios run -1..1 (smi_wsi_gap -2..2).
thresholdbodyhl_cohort_bias: the index level to cross (default 0). hl_liq_proximity: the share of the way from entry price to liquidation price, strictly between 0 and 1 (default 0.8).
bandbodyhl_cohort_bias and hl_liq_proximity: hysteresis, greater than 0 (default 0.1). A bias alert fires when the index reaches threshold + band (up) or threshold - band (down); a proximity alert fires when the ratio reaches threshold and re-arms below threshold - band.
directionbodyhl_cohort_bias: which crossings notify. pm_price_move: which moves of the market's first outcome (Yes on a yes/no market) notify. Default both.
channelsbodyWhich kinds of the user's enabled channels to notify. email: any alert (a Hyperliquid or Polymarket alert mails the account address). webhook: any alert (register one with alerts_register_webhook; asking for a Hyperliquid or Polymarket alert's webhook the user has not registered is an error). Discord is temporarily off and is refused, as is any other type. A kind the user has no enabled channel of is dropped from a transfer alert, and a transfer alert left with no channel e-mails the account address: call alerts_list_channels to see what is set up. Whatever the channels, every match is recorded as an event in the feed read with alerts_events (kept 15 days), so leave channels empty for a Hyperliquid or Polymarket alert you will only read there.
cooldown_minutesbodyToken-wide transfer alerts (no address): minimum gap between notifications. Default 60. Address alerts and Hyperliquid and Polymarket alerts notify on every event and ignore it.

Call it

curl -X POST -H "X-API-KEY: $VIKE_API_KEY" -H "Content-Type: application/json" \
  "https://data.vike.io/v1/alerts/rules" \
  -d '{"label": "Big USDC moves", "chain": "eth", "token_symbol": "USDC", "min_value_usd": 1000000, "channels": ["email"]}'

The answer

Its shape, with illustrative values: these routes read and write your own account, so nothing here is a capture.

{
 "alert": {
  "id": 77,
  "label": "Big USDC moves",
  "alert_type": "transfer",
  "chain": "eth",
  "token_symbol": "USDC",
  "min_value_usd": 1000000.0,
  "max_value_usd": null,
  "min_amount": null,
  "address_conditions": [],
  "cooldown_minutes": 60,
  "max_triggers_day": 10,
  "notify_channel_ids": [
   12
  ],
  "is_active": true,
  "created_at": "2026-11-04T09:00:00+00:00",
  "policy": "cooldown",
  "hl": null,
  "pm": null
 }
}

Alerts · REST · CLI · MCP

Change an alert rule

Route

POST /v1/alerts/rules/{alert_id}/edit

CLI

vike-data alerts edit

MCP

alerts_edit · 2 credits

Cost

2 credits, one request of the hourly limit

Free API Archive Stream MCP

Change one of your alert rules: only the fields in the JSON body change (the input of the alerts_edit MCP tool, less alert_id, which is the path). is_active: false pauses it; address: "" removes its wallet condition; a Hyperliquid rule is checked again whole. Returns {"alert": {...}}. 404 for a rule that is not yours or does not exist, 400 for a refused change, 503 when a changed token could not be checked (nothing is stored: retry). 2 credits, none on an error.

Parameters

ParameterInDescription
alert_id requiredpathThe alert rule's id (from GET /v1/alerts/rules).
labelbody
token_symbolbody
chainbody
min_value_usdbody
max_value_usdbody
min_amountbodyWhole tokens of token_symbol; '' or 0 removes the amount floor.
addressbody0x + 40-hex EVM address; '' removes the wallet condition.
address_rolebody
walletbodyOne 0x + 40-hex address. Hyperliquid: the account to watch, required for hl_wallet, hl_wallet_orders and hl_liq_proximity, optional for hl_liquidation. Polymarket: the proxy wallet (the address of its vike.io/polymarket/wallet page and of polymarket_tape rows), required for pm_wallet_trade.
condition_idbodyPolymarket only: the market, its 0x + 64-hex condition id. Required for pm_market_trade, pm_price_move and pm_resolution; optional for pm_wallet_trade (one market of that wallet).
actionsbodyhl_wallet only: which fills notify. Default: open, close, flip, increase ('add' is accepted for increase); reduce is opt-in.
sidebodyhl_wallet_orders only: only buy or only sell orders. Leave out for both.
eventsbodyhl_wallet_orders only: placed = a new order appeared in the book, gone = an order left it (its outcome filled, partially_filled or canceled comes from the order's own fills). Default both. Each check's orders arrive together, as one event.
pnl_cohortsbodyhl_cohort_fill and hl_liquidation: only wallets in these realised-PnL cohorts (the ladder runs from '3x smart' = top 1% to '3x rekt' = bottom 5%). hl_cohort_fill REQUIRES at least one PnL or size cohort; for hl_liquidation empty = any cohort.
size_cohortsbodyhl_cohort_fill and hl_liquidation: only wallets in these account-size cohorts (the ladder runs from '4x whale' = $10M+ to 'shrimp' = under $1k). hl_cohort_fill REQUIRES at least one PnL or size cohort; for hl_liquidation empty = any size.
indexbodyhl_cohort_bias only, required: the index watched for token_symbol. smi = long share of smart wallets minus long share of rekt wallets; wsi = long share of whales minus long share of shrimp; smart_bias / whale_bias = (long - short) / (long + short) of that cohort; smi_wsi_gap = smi - wsi. Ratios run -1..1 (smi_wsi_gap -2..2).
thresholdbodyhl_cohort_bias: the index level to cross (default 0). hl_liq_proximity: the share of the way from entry price to liquidation price, strictly between 0 and 1 (default 0.8).
bandbodyhl_cohort_bias and hl_liq_proximity: hysteresis, greater than 0 (default 0.1). A bias alert fires when the index reaches threshold + band (up) or threshold - band (down); a proximity alert fires when the ratio reaches threshold and re-arms below threshold - band.
directionbodyhl_cohort_bias: which crossings notify. pm_price_move: which moves of the market's first outcome (Yes on a yes/no market) notify. Default both.
channelsbody
cooldown_minutesbody
is_activebody

Call it

curl -X POST -H "X-API-KEY: $VIKE_API_KEY" -H "Content-Type: application/json" \
  "https://data.vike.io/v1/alerts/rules/77/edit" \
  -d '{"min_value_usd": 5000000, "channels": ["webhook"]}'

The answer

Its shape, with illustrative values: these routes read and write your own account, so nothing here is a capture.

{
 "alert": {
  "id": 77,
  "label": "Big USDC moves",
  "alert_type": "transfer",
  "chain": "eth",
  "token_symbol": "USDC",
  "min_value_usd": 5000000.0,
  "max_value_usd": null,
  "min_amount": null,
  "address_conditions": [],
  "cooldown_minutes": 60,
  "max_triggers_day": 10,
  "notify_channel_ids": [
   12
  ],
  "is_active": true,
  "created_at": "2026-11-04T09:00:00+00:00",
  "policy": "cooldown",
  "hl": null,
  "pm": null
 }
}

Alerts · REST · CLI · MCP

Delete an alert rule

Route

POST /v1/alerts/rules/{alert_id}/delete

CLI

vike-data alerts delete

MCP

alerts_delete · 2 credits

Cost

2 credits, one request of the hourly limit

Free API Archive Stream MCP

Delete one of your alert rules: paused and kept by default, removed for good with hard=true. {"ok": true, "alert_id", "hard"}. 404 for a rule that is not yours or does not exist. 2 credits, none on an error.

Parameters

ParameterInDescription
alert_id requiredpathThe alert rule's id (from GET /v1/alerts/rules).
hardqueryRemove the rule with its history, events and queued deliveries. Default false: the rule is paused and kept.

Call it

curl -X POST -H "X-API-KEY: $VIKE_API_KEY" \
  "https://data.vike.io/v1/alerts/rules/77/delete?hard=true"

The answer

Its shape, with illustrative values: these routes read and write your own account, so nothing here is a capture.

{
 "ok": true,
 "alert_id": 77,
 "hard": true
}

Alerts · REST · CLI · MCP

Your notification channels

Route

GET /v1/alerts/channels

CLI

vike-data alerts channels

MCP

alerts_list_channels · 0 credits

Cost

0 credits, one request of the hourly limit

Free API Archive Stream MCP

Your enabled e-mail and webhook channels: {"channels": [{id, channel_type, is_verified, label}]}; a webhook's label is the first 40 characters of its URL. A channel id is what a rule's notify_channel_ids and the routes below take. Free (0 credits), like the alerts_list_channels MCP tool.

Parameters

None.

Call it

curl -H "X-API-KEY: $VIKE_API_KEY" \
  "https://data.vike.io/v1/alerts/channels"

The answer

Its shape, with illustrative values: these routes read and write your own account, so nothing here is a capture.

{
 "channels": [
  {
   "id": 11,
   "channel_type": "email",
   "is_verified": true,
   "label": "you@example.com"
  },
  {
   "id": 12,
   "channel_type": "webhook",
   "is_verified": true,
   "label": "https://example.com/vike-alerts"
  }
 ]
}

Register an https:// webhook as a channel and send it one signed test event now. The JSON body is the input of the alerts_register_webhook MCP tool: url, and optionally your own secret (16+ characters). Without one a secret is generated and returned ONCE here as secret (also shown on Account -> Alerts -> channels); every delivery is signed with it (HMAC-SHA256, X-Vike-Signature). test is {ok, status} or {ok: false, error}; a failing test does not refuse the channel. Cache-Control: no-store. 2 credits, none on an error.

Parameters

ParameterInDescription
url requiredbodyWebhook URL, https:// only. Must be reachable from our server.
secretbodyOptional HMAC secret (>=16 chars). If omitted, we generate one and return it as secret in the answer.

Call it

curl -X POST -H "X-API-KEY: $VIKE_API_KEY" -H "Content-Type: application/json" \
  "https://data.vike.io/v1/alerts/channels/webhooks" \
  -d '{"url": "https://example.com/vike-alerts"}'

The answer

Its shape, with illustrative values: these routes read and write your own account, so nothing here is a capture.

{
 "ok": true,
 "channel_id": 12,
 "channel_type": "webhook",
 "url_preview": "https://example.com/vike-alerts",
 "signed": true,
 "secret": "<shown once here, and again on Account → Alerts → channels>",
 "secret_note": "…",
 "test": {
  "ok": true,
  "status": 200
 }
}

Alerts · REST · CLI · MCP

Rotate a webhook's signing secret

Route

POST /v1/alerts/channels/{channel_id}/rotate-secret

CLI

vike-data alerts rotate-secret

MCP

alerts_rotate_webhook_secret · 2 credits

Cost

2 credits, one request of the hourly limit

Free API Archive Stream MCP

Give one of your webhook channels a new generated signing secret, returned as secret. The old secret keeps signing for 24 hours (previous_secret_valid_until): until then each delivery carries two sig= values, and a receiver must accept it when ANY of them matches. 404 for a channel that is not yours, not a webhook or does not exist. Cache-Control: no-store. 2 credits, none on an error.

Parameters

ParameterInDescription
channel_id requiredpathThe webhook channel's id (from GET /v1/alerts/channels).

Call it

curl -X POST -H "X-API-KEY: $VIKE_API_KEY" \
  "https://data.vike.io/v1/alerts/channels/12/rotate-secret"

The answer

Its shape, with illustrative values: these routes read and write your own account, so nothing here is a capture.

{
 "ok": true,
 "channel_id": 12,
 "secret": "<the new secret>",
 "secret_note": "…",
 "previous_secret_valid_until": "2026-11-05T09:00:00+00:00"
}

Alerts · REST · CLI · MCP

Send a webhook the test event again

Route

POST /v1/alerts/channels/{channel_id}/test

CLI

vike-data alerts test-webhook

MCP

alerts_test_webhook · 1 credit

Cost

1 credit, one request of the hourly limit

Free API Archive Stream MCP

Send the signed test event to one of your webhook channels again, for example after the receiver installed a new secret. {"ok": true, "channel_id", "test"}, test being {ok, status} when the endpoint answered or {ok: false, error} (timeout, network, ssrf_refused,...). Nothing is queued or retried. 404 for a channel that is not yours, not a webhook or does not exist. 1 credit, none on an error.

Parameters

ParameterInDescription
channel_id requiredpathThe webhook channel's id (from GET /v1/alerts/channels).

Call it

curl -X POST -H "X-API-KEY: $VIKE_API_KEY" \
  "https://data.vike.io/v1/alerts/channels/12/test"

The answer

Its shape, with illustrative values: these routes read and write your own account, so nothing here is a capture.

{
 "ok": true,
 "channel_id": 12,
 "test": {
  "ok": true,
  "status": 200
 }
}

Alerts · REST · CLI · MCP

Your alert events, oldest first

Route

GET /v1/alerts/events

CLI

vike-data alerts events

MCP

alerts_events · 1 credit

Cost

1 credit, one request of the hourly limit

Free API Archive Stream MCP

The events your alert rules matched, oldest first, one row per (rule, event).

{"events": [{id, rule_id, alert_label, alert_type, created_at, event, delivery}], "next_cursor", "has_more"}. event is the event as the webhook sends it; delivery lists each channel's delivery (an empty list for a rule with no channel: the feed is the channel): id, channel, channel_key, status, attempts, last_attempt_at, next_attempt_at (while it is waiting), last_error as a code only (http 500, timeout, network,...). Poll with next_cursor: an empty page returns the cursor it was given. A page also ends once its events pass about 2 MB (a wallet-orders event can hold 200 orders): it may then hold fewer than limit events with has_more true; keep paging with next_cursor, nothing is skipped. Events and deliveries are kept 15 days. One credit.

Parameters

ParameterInDescription
cursorqueryPass back the previous next_cursor verbatim. Empty reads from the oldest event still kept (15 days).
limitqueryDefault 100, clamped to 1-500
rule_idqueryOnly the events of this alert rule

Call it

curl -H "X-API-KEY: $VIKE_API_KEY" \
  "https://data.vike.io/v1/alerts/events?limit=50&rule_id=77"

Alerts · REST · CLI · MCP

Send one of your deliveries again

Route

POST /v1/alerts/deliveries/{delivery_id}/resend

CLI

vike-data alerts resend

MCP

alerts_resend_delivery · 1 credit

Cost

1 credit, one request of the hourly limit

Free API Archive Stream MCP

Send one of your own deliveries again: the id of an item of an event's delivery list (from /v1/alerts/events).

Whatever its status (sent, retrying, expired, skipped), up to 15 days after its event. It goes back to the sender for an immediate attempt with the same event id, a fresh signature and the same X-Vike-Delivery-Id, and the sender then retries it as it would a new one (72 h). {"id", "status", "result"}: result is queued, already_queued (it was already waiting: a second call changes nothing) or sending (an attempt is in flight right now). queued is a request, not a promise: the sender checks the rule and the channel again, so a delivery whose rule is off or whose channel is disabled or deleted is skipped again, and a paused channel delays the attempt. 404 for a delivery that is not yours or does not exist, 410 past 15 days (404 once the hourly prune removed it), 409 for an unsupported delivery (its channel cannot carry that kind of event, an event with no e-mail template: it would fail the same way). One credit; none on an error.

Parameters

ParameterInDescription
delivery_id requiredpathThe delivery's id, from an event's delivery list (GET /v1/alerts/events).

Call it

curl -X POST -H "X-API-KEY: $VIKE_API_KEY" \
  "https://data.vike.io/v1/alerts/deliveries/5531/resend"

The answer

Its shape, with illustrative values: these routes read and write your own account, so nothing here is a capture.

{
 "id": 5531,
 "status": "retry",
 "result": "queued"
}

The alert feed and signed webhooks

What your rules produced, the webhook body, how to verify a delivery, and every event shape.

Every rule you own produces events: one per transfer, fill, liquidation, index crossing, liquidation-risk crossing or Polymarket trade, price move or resolution it matched. Each is sent to the rule's channels and kept for 15 days in a feed you read with GET /v1/alerts/events or the alerts_events tool; a rule with no channel fills the feed only.

The answer's shape (illustrative values, not a capture)

{
 "events": [
  {
   "id": 1042,
   "rule_id": 77,
   "alert_label": "Whale BTC fills",
   "alert_type": "hl_wallet",
   "created_at": "2026-10-28T09:14:41.200Z",
   "event": {
    "schema": "vike.event.v1",
    "event_id": "hl.fills:0x1111111111111111111111111111111111111111:1793178870482:123456789",
    "source": "hl.fills",
    "kind": "fill",
    "ts": "2026-10-28T09:14:30.482Z",
    "cursor": "1793178870482-00000000000123456789",
    "chain": "hyperliquid",
    "subject": {
     "wallet": "0x1111111111111111111111111111111111111111",
     "symbol": "BTC"
    },
    "data": {
     "action": "open",
     "side": "buy",
     "size_usd": 1250000.0,
     "size_token": 12.5,
     "price": 100000.0,
     "closed_pnl": 0.0,
     "start_position": 0.0,
     "position_after": 12.5,
     "crossed": true,
     "trade_id": 123456789,
     "dex": "HL",
     "fills": 1
    },
    "labels": {
     "pnl_cohort": "2x smart",
     "pnl_cohort_basis": "live",
     "pnl_cum_usd": 4210000.0,
     "size_cohort": "3x whale"
    }
   },
   "delivery": [
    {
     "id": 5531,
     "channel": "webhook",
     "channel_key": "12",
     "status": "sent",
     "attempts": 1,
     "last_attempt_at": "2026-10-28T09:14:42.010Z",
     "next_attempt_at": null,
     "last_error": null
    }
   ]
  }
 ],
 "next_cursor": "eyJpZCI6MTA0Mn0",
 "has_more": false
}
FieldWhat it is
eventsThe page, oldest first; empty when nothing is newer than cursor
next_cursor, has_morePass next_cursor back as cursor; an empty page returns the cursor you sent, so keep it and ask again
idThe feed's row for this (rule, event) pair; the event's own id is event.event_id
rule_id, alert_label, alert_typeThe rule that matched
created_atWhen the event was recorded, UTC, milliseconds
eventExactly the event the webhook carries (Event shapes)
deliveryOne entry per channel: id (what send again takes), channel, channel_key, status (pending, retry, sent, expired after 72 hours, skipped, unsupported), attempts, last_attempt_at, next_attempt_at and last_error as a code only: http 500, timeout, network, tls, blocked, expired or error
  • Events appear in the order they are numbered, as soon as they are recorded (before the e-mail or webhook goes out), so a poller that always passes back the last next_cursor misses none and sees none twice.
  • A cursor older than 15 days does not fail: the feed continues from the oldest event still kept.
  • Deleting a rule for good (hard=true) removes its events; pausing keeps them.
  • 400 for a cursor that is not one, 422 for a limit or rule_id that is not an integer, 503 when the feed could not be read.

The webhook body

A webhook channel receives the same event as a signed POST. Every rule type sends one envelope:

FieldWhat it is
schemavike.alert.v1
alert_id, alert_label, alert_typeThe rule: its id, its name (alert #<id> when it has none) and its type
triggered_atThe event's time, ISO 8601 with a +00:00 offset
eventThe event, as in the feed

A transfer body also carries the flat keys of the first webhook format, so an old receiver keeps working: chain, token, value_usd, and from_address, to_address and tx_hash as bare lowercase hex without 0x.

Delivery is at least once: dedupe on alert_id and event.event_id from the body, the pair the signature covers. X-Vike-Delivery-Id is the same on every try of one delivery and X-Vike-Attempt counts them. Answer any 2xx within 20 seconds; a failed delivery is retried with back-off for 72 hours, and a 429 with Retry-After pauses that webhook. Every delivery is listed under Account → Alerts → Webhook log.

Verifying the signature

Each POST carries these headers. Compute HMAC-SHA256(secret, "<t>.<raw body>") with the t= of the header, hex-encode it and compare it in constant time with every sig= value: accept when any matches (two for 24 hours after a secret rotation). Check the raw bytes before any JSON parser touches them, and reject a timestamp more than about five minutes old.

Content-Type: application/json
User-Agent: vike-alerts/1.0
X-Vike-Timestamp: <unix epoch seconds>
X-Vike-Signature: t=<ts>,sig=<hex hmac>[,sig=<hex hmac>]
X-Vike-Event-Id: <event_id>
X-Vike-Attempt: <1 for the first try>
X-Vike-Delivery-Id: <this delivery's id, the same on every try>

Verify a delivery

import hashlib
import hmac
import time

def verify(secret: str, raw_body: bytes, signature_header: str, tolerance_s: int = 300) -> bool:
    """raw_body is the request body bytes exactly as received (do not re-serialise the JSON).
    Hostile input (a non-ASCII sig, \`t=²\`, 5000 digits, a lone surrogate, no header) is a False, never an exception."""
    try:
        parts = [p.split("=", 1) for p in signature_header.split(",") if "=" in p]
        ts = next((v for k, v in parts if k == "t"), "")
        if not ts.isdecimal() or len(ts) > 12 or abs(time.time() - int(ts)) > tolerance_s:   # replay window
            return False
        want = hmac.new(secret.encode(), f"{ts}.".encode() + raw_body, hashlib.sha256).hexdigest().encode()
        return any(hmac.compare_digest(want, v.encode()) for k, v in parts if k == "sig")   # ANY sig= may match
    except (ValueError, TypeError, AttributeError, OverflowError):     # UnicodeError is a ValueError
        return False

Event shapes

Every event, in the feed, a webhook or the WebSocket, has the same ten fields: schema (vike.event.v1), event_id (stable, unique, starts with its source), source, kind, ts (when it happened, UTC, milliseconds), cursor (a position in its source, not the feed's cursor), chain, subject, data and labels. kind says which subject and data to expect:

kindRule typessubjectdata
transfertransferfrom, to, token_address, token_symbol, tx_hashvalue_usd (null when unpriced), priced, block_number, log_index, tx_hash, transfer_type
fillhl_wallet, hl_cohort_fillwallet, symbolaction, side, size_usd, size_token, price, closed_pnl, start_position, position_after, crossed, trade_id, dex; fills on hl_wallet (one event merges a check's fills per coin, action and side)
liquidationhl_liquidationwallet, symboldirection, size_usd, size_token, mark_price, inferred
indexhl_cohort_biassymbolindex, value, threshold, band, from_side, to_side, snapshot_ts
liq_riskhl_liq_proximitywallet, symbolasset, direction, liq_ratio, threshold, band, mark_price, entry_price, liquidation_price, size_usd, leverage, snapshot_ts
wallet_ordershl_wallet_orderswalletorders: every order of the check, each with action (placed or gone), symbol, side, order_type (limit or trigger), trigger_type, limit_px, trigger_px, size, notional_usd, placed_at, slot_at, dex, oid, and for a gone one last_seen, outcome, filled_size, filled_usd (at most 200 orders an event); beside it part and parts and the check's totals n_placed, n_gone, placed_usd, gone_usd, n_filled, n_partially_filled, n_canceled, n_unknown and slots
pm_tradepm_wallet_trade, pm_market_tradewallet, condition_id, token_id, slug, title, outcome, outcome_indexside, size, price, usd, role, tx_hash, log_index, block_number
pm_price_movepm_price_movetoken_id, condition_id, slug, outcome, outcome_indexmid_from, mid_to, move, direction, window_s, bid, ask
pm_resolutionpm_resolutioncondition_id, slug, question, outcomeswinning_index, winning_outcome, payouts, invalid, resolved_block, traded
  • labels holds the cohorts of subject.wallet when they were looked up (pnl_cohort, pnl_cohort_basis, pnl_cum_usd, size_cohort); a missing key means unknown, never a cohort. A Polymarket trade carries the wallet's name, pseudonym and tiers.
  • A fill's trade_id can be larger than 2^53, which JavaScript reads inexactly: take it from the end of event_id.

Ask: “What have my alerts fired since yesterday?”

WebSocket: live events on stream.vike.io

The rules every channel shares: connect, subscribe, filter, resume, close codes. Each product's channels are on its own page.

  1. Connect wss://stream.vike.io/v1/stream/hl (or another address) with X-API-KEY on the handshake.
  2. Subscribe Send {"action": "subscribe", "id", "channel", "filter"}.
  3. Resume Keep each frame's cursor; after a drop, subscribe with it and get what you missed, up to 3 days back.
import json, os
from websockets.sync.client import connect   # websockets >= 14

with connect("wss://stream.vike.io/v1/stream/hl",
             additional_headers={"X-API-KEY": os.environ["VIKE_API_KEY"]}) as ws:
    ws.send(json.dumps({"action": "subscribe", "id": "s1", "channel": "hl.liquidations",
                        "filter": {"coin": ["BTC", "ETH"]}}))
    for message in ws:
        frame = json.loads(message)
        if frame["type"] == "ping":
            ws.send(json.dumps({"action": "pong"}))
        elif frame["type"] == "event":
            e = frame["event"]
            print(json.dumps(e))   # keep frame["cursor"] to resume after a drop

22 channels on 4 addresses, all on stream.vike.io. Connect to an address, then subscribe to one or more of its channels, each with an optional filter. One key works on every address; a channel belongs to one, and asking for it on another is unknown_channel. Each product's channels, their fields and a real event per channel are on its page.

AddressProductChannelsREST catch-up
wss://stream.vike.io/v1/stream/onchainOn-chainchain.transfersGET /v1/onchain/stream-events
wss://stream.vike.io/v1/stream/hlHyperliquidhl.candles, hl.closed_trades, hl.fills, hl.funding, hl.ledger, hl.leverage, hl.liq_risk, hl.liquidations, hl.order_walls, hl.positions, hl.top_positions, hl.twapsGET /v1/hyperliquid/stream-events
wss://stream.vike.io/v1/stream/hl-cohortsHL Cohortshl.cohort_bias, hl.cohort_flow, hl.cohort_leverage, hl.indicesGET /v1/hl-cohorts/stream-events
wss://stream.vike.io/v1/stream/polymarketPolymarketpm.new_markets, pm.positions, pm.price_moves, pm.resolutions, pm.tradesGET /v1/polymarket/stream-events
  • Limits: 1 connection per key per address, 3 per account per address, 100 subscriptions on one connection. A subscription's list filter takes many values (wallet up to 1,000), so the cap counts subscriptions, not wallets.
  • Ping: the server sends {"type": "ping"} every 15 s; answer with the text frame {"action": "pong"} (a protocol-level pong does not count). 3 missed pongs close the connection with 4412.
  • Delivery is at least once: dedupe on event.event_id.
  • Not counted: connecting, subscribing and reconnecting spend no hourly budget and no credits, never answer 429 and never close for a rate limit.
  • Compression: a client that offers permessage-deflate gets compressed frames (each message inflates on its own); one that offers nothing gets plain frames.

Frames

You sendFieldsWhat it does
subscribeid, channel, filter, cursorStarts a subscription. id is yours: 1-32 characters of A-Z a-z 0-9 _ -, unique per connection. filter is optional (Filters); cursor resumes.
unsubscribeidEnds one subscription.
pongnoneAnswers a ping.

JSON text frames only, at most 64 KiB each. The server sends subscribed (with the channel's current cursor and a replay summary), event (one event, ids listing every subscription it matched, and the frame's cursor), ping, gap and error. Keep the frame's cursor, never the cursor inside event (that one is the source's position). The event itself has the envelope under Event shapes; drop a repeat by event.event_id.

Channels run on demand

A channel is produced only while someone subscribes to it (and 15 minutes after): the first events of an idle channel come with its source's next pass, and an idle window is one gap frame in cursor order. So the first events of a channel nobody was watching are new ones, not history: they come with the source's next pass (seconds for fills, about 12 minutes for position captures). While one client is subscribed, the channel runs for everyone.

Channel by channel

Each event's event_id is built from the parts below, so it is the same every time the event arrives; what a channel holds beyond its filters; and when its first events come after an idle start. Fields and a real event of each channel are on its product's page.

Channelevent_idWhat is in itFirst events
hl.fillshl.fills:<wallet>:<fill ms>:<trade id>Every perp fill; without min_usd, fills of $1,000 or more. crossed true is the taker.within about 10 s of the fill reaching our store; our node can run a minute or more behind the venue
hl.positionshl.positions:<wallet>:<fill ms>:<trade id>The same fill again, as its own event, when it is worth $1,000 or more (a floor you cannot lower), could be classified, and the wallet is shark or bigger or the position before or after it is worth $250,000 or more. Adds leverage, liquidation_px and position_ts from the last capture that held the position (up to about 25 minutes old, null on an open) and labels.tags, the wallet's trader tags (the tag filter).with hl.fills
hl.liquidationshl.liquidations:<wallet>:<coin>:<ms>Every liquidation; direction is the side that was closed. inferred is false for one read from the liquidated account's own fill (every one since 2026-11-01), true for an older one inferred from position snapshots.checked every minute
hl.liq_riskhl.liq_risk:<wallet>:<coin>:<long|short>:<capture s>A position of $100,000 or more whose liq_ratio (0 at entry, 1 at the liquidation price) reaches 0.80, announced once; it re-arms below 0.70 or when the position closes. prev is "out" when it has just entered, null on the channel's first capture, which announces every position already in the zone. Not the alert rule's liq_risk event.with the next position capture, about every 12 minutes
hl.order_wallshl.order_walls:<order id>:appeared or :disappearedPerp orders of $1,000,000 or more: resting limit orders (limit price × original size, kind resting) and stop orders not yet triggered (trigger price × original size, kind trigger, with trigger_type and trigger_px); spot is not included. The book is read in 5-minute slots (source l4_5m; the ~12-minute snapshots, state_12m, while those stop): ts is the slot, ts_placed the placement, a wall that lives between two slots is in neither. disappeared says what happened: outcome filled, partially_filled, canceled, or unknown when its fills were still not in 10 minutes later, and filled_usd; it can come a few minutes after the slot while the fills arrive.with the next 5-minute slot (checked every minute)
hl.twapshl.twaps:<twap id>:<line µs>:<status>One event per TWAP status line; error is a rejected placement. size_usd is the order's whole size × the coin's mid at the newest 5-minute snapshot, null with no mid in 15 minutes.the node's TWAP lines are read every minute: a minute or two after the line
hl.ledgerhl.ledger:<wallet>:<ms>:<hash>:<12 hex of event type, token and nonce> (- without a hash)Deposits, withdrawals, transfers, vault and lending lines; a transfer is two events, one per wallet. A line stored more than 5 minutes late is not streamed.about a minute behind the node
hl.leveragehl.leverage:<wallet>:<coin>:<capture s>A leverage or margin-mode change seen between two position captures; one made and undone between them is never seen.with the position captures, about every 12 minutes
hl.fundinghl.funding:<coin>:<snapshot s>Every perp's funding rate, premium, mid and open interest at each 5-minute snapshot, HIP-3 coins included, spot not.every 5 minutes
hl.candleshl.candles:<interval>:<asset>:<bar open s>Closed bars only, never a forming or a revised one. A spot pair's symbol is its token name (HYPE) and asset keeps Hyperliquid's id (@107), so coin: ["HYPE"] matches the perp and the spot pair: add market for one.30 to 60 seconds after a bar ends
hl.top_positionshl.top_positions:<coin>:<wallet>:<entered|left|moved>:<capture s>The 10 largest positions of each of the 50 coins whose top 10 is worth most: one event when a position enters, leaves or changes rank. The first capture, and a coin joining the 50, send its whole top 10 as entered with first: true.with the next position capture, about every 12 minutes
hl.closed_tradeshl.closed_trades:<wallet>:<coin>:<long|short>:<open trade id>A round trip that closed, sent once; open_tid can exceed 2^53, so read it from the event_id.in batches, every 30 minutes
hl.cohort_flowhl.cohort_flow:<coin>:<axis>:<cohort, spaces as _>:<minute s>, plus :r1 on a revisionPer closed minute, coin, axis and cohort: every fill, each side counted, unknown included. Each minute is read again about 150 seconds after it ends; a coin whose count changed is sent again with rev 1 and full totals: keep the highest rev. After a catch-up, order by window_start.about a minute after each minute closes
hl.indiceshl.indices:<coin>:<capture s>The five indices of the 50 coins with the most open position value; crossed lists those that moved from at or below −0.10 to at or above +0.10, or back (a coin's first sighting crosses nothing). An index filter keeps only the events where that index crossed.every position capture, about every 12 minutes
hl.cohort_biashl.cohort_bias:<coin>:<axis>:<cohort, spaces as _>:<capture s>Long and short value per coin, axis and cohort for the same 50 coins; unknown included, so an axis adds up to the coin's book.every position capture
hl.cohort_leveragehl.cohort_leverage:<axis>:<cohort, spaces as _>:<hour s>Each cohort's hourly leverage against its wallets' account value; an hour rebuilt from fills is not sent.the newest hour at once, then each hour about an hour after it starts
chain.transferschain.transfers:<chain>:<tx hash>:<log index>, :native for the native coinchain is eth, not ethereum. Sent once pricing has run over the block: an unpriced transfer has value_usd null and priced false, and no min_usd filter passes it. A repaired block is not sent again. Fields and ready-made filters: Whale alerts.1 to 6 minutes after the block
pm.tradespm.trades:<tx hash>:<log index>One event per maker order's fill: a match is the makers' events plus one taker event of their summed size, so add usd over one role only.read every 15 seconds, about 4 minutes behind the chain (150 blocks of confirmations)
pm.positionspm.positions:<tx hash>:<log index>What each trade did to the wallet's position in that outcome; under 0.01 shares is flat. Shares count trades only: a split, merge or transfer is not on the tape, so a position can go below zero.with pm.trades
pm.resolutionspm.resolutions:<condition id>Every on-chain resolution, traded or not (data.traded); winning_index is -1 when the top payout is shared.checked every minute
pm.price_movespm.price_moves:<token id>:<5-minute bucket>:<up|down>A mid move of 0.05 or more within five minutes, per outcome token; a move across a bucket boundary can appear twice.checked every 30 seconds
pm.new_marketspm.new_markets:<condition id>A market the order-book recorder sees for the first time, sent once; class is updown_5m, updown_15m or other. One first seen while the channel was idle is never sent later.within about a minute of the recorder seeing it

One connection per key

A new connection is always accepted; an older one of the same key (or past the account's 3 on a path) is closed 4410 with a resume cursor, never refused. Two programs on one key therefore replace each other forever: after a 4410, do not reconnect automatically. Use one key per program, at most three programs per account, on each address.

Resuming

  • A subscribe without a cursor gets new events from now; with a cursor it gets what it missed, up to 3 days back, oldest first, then the live events, with nothing skipped or doubled at the hand-over.
  • Keep one cursor per subscription, not one per connection: across subscriptions, the replay of one can arrive after live events of another. Cursors are fixed-width strings, so a later one compares greater as text.
  • Whenever a connection ends, open a new one at once and subscribe with each subscription's last cursor. There is no waiting list and no penalty. The exceptions: a refused or revoked credential, and 4410.
  • The subscribed frame's replay says how the missed events come: from none (no cursor, nothing replayed), buffer (recent ones; events is how many follow) or store (older ones, in pages; events null).
  • Resuming from the cursor of a 4408, 4409 or 4410 frame can repeat a few events that were being sent: drop them by event_id. Such a frame names one channel; resume the connection's other subscriptions from your own cursors.
  • Without a socket, the same stored events are the REST catch-up of each path (GET /v1/<product>/stream-events) and the stream_events tool.

Keep one cursor per subscription, and resume with it

import json

cursors = {}      # subscription id -> cursor of the last event handled on it
channels = {}     # subscription id -> its channel
handled = set()   # event ids already handled (keep the last few thousand)

def on_message(text, send):
    frame = json.loads(text)
    if frame["type"] == "event":
        if frame["event"]["event_id"] not in handled:
            handled.add(frame["event"]["event_id"])
            handle(frame["event"])         # your code
        for sub_id in frame["ids"]:
            cursors[sub_id] = max(cursors.get(sub_id, ""), frame["cursor"])
    elif frame["type"] == "subscribed":
        cursors.setdefault(frame["id"], frame["cursor"])
        channels[frame["id"]] = frame["channel"]
    elif frame["type"] == "gap":           # about the channel: every subscription on it moves on
        for sub_id, channel in channels.items():
            if channel == frame["channel"]:
                cursors[sub_id] = max(cursors.get(sub_id, ""), frame["cursor"])
    elif frame["type"] == "ping":
        send(json.dumps({"action": "pong"}))

def subscribe_all(send, subscriptions):
    for sub in subscriptions:          # {"id": ..., "channel": ..., "filter": ...}
        message = {"action": "subscribe", **sub}
        if sub["id"] in cursors:
            message["cursor"] = cursors[sub["id"]]
        send(json.dumps(message))

Gap frames

A window in which a channel was not running is never filled in afterwards. A client that resumes across one gets one gap frame for it (channel, cursor, from, to, reason: "channel_idle"), in order and with no ids: apply its cursor to every subscription on that channel. It says the channel did not run, not that nothing happened: read the window from the product's REST routes if you need it.

Authenticating the handshake

Send the X-API-KEY header (or Authorization: Bearer <OAuth token>) on the handshake.?token=<key> exists only for a browser, which cannot set WebSocket headers: a key in a URL can end up in proxy and browser logs, so never use it where a header is possible. The handshake tries the Authorization bearer, then X-API-KEY, then ?token=, and stops at the first that produces an identity. The stream checks every open connection's credential about once a minute and closes it with 4401 when it stopped being valid.

Filters

A list is OR inside a key, keys are AND across, an empty filter matches everything. A cohort or tier value unknown also matches an event with no label. min_amount needs token. A key a channel does not take is refused with bad_subscription, as is a bad value (with the key and the reason). A list key takes a JSON list ({"coin": ["BTC", "ETH"]}), a one-value key one string ({"side": "buy"}), a number key one number, contract a JSON boolean. Check a filter without connecting with stream_check_filter or vike-data stream check.

KeyTakesValues or formatChannels
actionlist of up to 50close, flip, increase, open, reducehl.fills, hl.positions, pm.positions
axisone valuepnl, sizehl.cohort_bias, hl.cohort_flow, hl.cohort_leverage
chainlist of up to 50base, bsc, eth, hyperevmchain.transfers
classlist of up to 50other, updown_15m, updown_5mpm.new_markets
cohortlist of up to 502x dolphin, 2x fish, 2x loser, 2x rekt, 2x shark, 2x shrimp, 2x smart, 2x whale, 2x winner, 3x loser, 3x rekt, 3x smart, 3x whale, 3x winner, 4x whale, dolphin, fish, loser, rekt, shark, shrimp, smart, unknown, whale, winnerhl.cohort_bias, hl.cohort_flow, hl.cohort_leverage
coinlist of up to 200not a coin name (A-Z, 0-9 and: @. _ / -, at most 32 characters)hl.candles, hl.closed_trades, hl.cohort_bias, hl.cohort_flow, hl.fills, hl.funding, hl.indices, hl.leverage, hl.liq_risk, hl.liquidations, hl.order_walls, hl.positions, hl.top_positions, hl.twaps
contractbooleanchain.transfers
dexlist of up to 50not a dex name (A-Z and 0-9, at most 16 characters; HL is the core venue)hl.candles
directionone valuelong, shorthl.closed_trades, hl.leverage, hl.liq_risk, hl.liquidations, hl.top_positions
entitylist of up to 50must be a printable name of 1 to 64 characterschain.transfers
eventone valueappeared, disappearedhl.order_walls
eventone valueentered, left, movedhl.top_positions
event_typelist of up to 50accountClassTransfer, borrowLend, deposit, internalTransfer, rewardsClaim, send, spotTransfer, subAccountTransfer, vaultCreate, vaultDeposit, vaultDistribution, vaultLeaderCommission, vaultWithdraw, withdrawhl.ledger
indexlist of up to 50smart_bias, smi, smi_wsi_gap, whale_bias, wsihl.indices
intervallist of up to 5015m, 1d, 1h, 1m, 30m, 4h, 5mhl.candles
kindone valueresting, triggerhl.order_walls
marketlist of up to 200markets are condition ids: 0x followed by 64 hex characterspm.positions, pm.price_moves, pm.resolutions, pm.trades
marketlist of up to 50perp, spothl.candles
min_amountnumbermust be a finite number >= 0chain.transfers
min_leveragenumbermust be a finite number >= 0hl.positions
min_movenumbermust be a finite number >= 0; must be a price move above 0 and at most 1 (0.05 = five points)pm.price_moves
min_usdnumbermust be a finite number >= 0chain.transfers, hl.closed_trades, hl.fills, hl.ledger, hl.leverage, hl.liq_risk, hl.liquidations, hl.order_walls, hl.positions, hl.top_positions, hl.twaps, pm.positions, pm.trades
moveone valuedown, uppm.price_moves
pnl_cohortlist of up to 502x loser, 2x rekt, 2x smart, 2x winner, 3x loser, 3x rekt, 3x smart, 3x winner, loser, rekt, smart, unknown, winnerhl.closed_trades, hl.fills, hl.ledger, hl.leverage, hl.liq_risk, hl.liquidations, hl.order_walls, hl.positions, hl.top_positions, hl.twaps
pnl_tierlist of up to 50belowmedian, bot1, bot10, bot5, median, top1, top10, top5, unknown, unrankedpm.trades
roleone valuemaker, takerpm.trades
sideone valuebuy, sellhl.fills, hl.order_walls, hl.positions, hl.twaps, pm.trades
size_cohortlist of up to 502x dolphin, 2x fish, 2x shark, 2x shrimp, 2x whale, 3x whale, 4x whale, dolphin, fish, shark, shrimp, unknown, whalehl.closed_trades, hl.fills, hl.ledger, hl.leverage, hl.liq_risk, hl.liquidations, hl.order_walls, hl.positions, hl.top_positions, hl.twaps
statuslist of up to 50activated, error, finished, stopped, terminated, waitingForTriggerhl.twaps
taglist of up to 50must be a printable name of 1 to 64 charactershl.positions
tokenlist of up to 200tokens are 0x followed by 40 hex characters (the token contract address)chain.transfers
token_idlist of up to 200token ids are 1 to 80 decimal digitspm.price_moves
volume_tierlist of up to 50dolphin, fish, shrimp, unknown, whalepm.trades
walletlist of up to 1,000wallets are 0x followed by 40 hex characterschain.transfers, hl.closed_trades, hl.fills, hl.ledger, hl.leverage, hl.liq_risk, hl.liquidations, hl.order_walls, hl.positions, hl.top_positions, hl.twaps, pm.positions, pm.trades
wallet_typelist of up to 50must be a printable name of 1 to 64 characterschain.transfers

Defaults: hl.fills min_usd 1,000, unless the filter sets it (0 removes it).

Close codes, refusals and error frames

Five closes come after an error frame with a cursor to resume from on that channel (4408 to 4412); 4401 sends one without. The close's reason is the same word as the frame's code; every other close has no frame, so keep your own cursors as you go.

CodeReasonWhat it means and what to do
4401key_revokedthe key or OAuth token no longer authenticates (revoked, or its account deactivated): no resume cursor; reconnect only with a valid credential
4408slow_consumerthe client stopped reading; resume from the last error frame's cursor
4409server_catching_upthe client reads, but the server got further ahead than its queue holds; resume
4410replaceda newer connection of the same key, or the account's 4th on this path, took over: do not auto-reconnect, or the two replace each other forever
4411memory_backstopthe worker's queues passed its memory budget and this connection held the largest backlog; resume
4412ping_timeoutno pong to the last 3 pings; resume
1011nonea server fault: reconnect with your cursor
1012nonea server restart: reconnect with your cursor
1009nonea message you sent was over 64 KiB: send smaller ones, reconnect, resume
1001, 1006, nonenonethe server went away or the connection dropped with no close frame: reconnect at once with your own cursors

4408 comes after 30 seconds in which frames waited and the client took none; a client that reads, however slowly, is closed 4409 instead. One that kept asking for replays it did not read gets a 4408 frame with no cursor and a message starting replay backlog full: resume from your own cursors.

Refused at the handshake

A connection refused before it opens is an HTTP answer to the upgrade request, not a close. The body is JSON (except Cloudflare's 521 / 522); its close_code names the number the refusal would carry as a close.

Statuscodeclose_codeMeaningWhat the client does
401unauthorized4401No valid credential.Fix the credential; a retry without one fails the same way.
503capacity4503The server process that took the connection is at its connection limit.Send the same upgrade request again.
503unavailable1013The credential could not be checked just now.Send the same upgrade request again.
521 or 522none: a Cloudflare HTML pagenoneThe Hyperliquid or HL Cohorts stream is stopped or restarting.Send the same upgrade request again, then resume.
503STREAM_UNAVAILABLEnoneThe on-chain or Polymarket stream is stopped or restarting.Send the same upgrade request again, then resume.
403STREAM_ORIGINnoneThe on-chain or Polymarket stream asked on data.vike.io.Use wss://stream.vike.io/v1/stream/onchain or /polymarket.
410STREAM_MOVEDnoneAn old address on data.vike.io (the Hyperliquid stream or the closed candle stream).Use wss://stream.vike.io/v1/stream/hl; candles are its hl.candles channel.

The edge's own bodies, as it answers them:

410 STREAM_MOVED: the closed candle stream, wss://data.vike.io/v1/stream (and wss://vike.io/v1/stream)

{"error":{"code":"STREAM_MOVED","message":"the candle stream moved to the hl.candles channel of wss://stream.vike.io/v1/stream/hl"}}

410 STREAM_MOVED: the old Hyperliquid address on data.vike.io

{"error":{"code":"STREAM_MOVED","message":"the stream moved to wss://stream.vike.io/v1/stream/hl"}}

403 STREAM_ORIGIN: the on-chain path on data.vike.io (Polymarket's names its own)

{"error":{"code":"STREAM_ORIGIN","message":"use wss://stream.vike.io/v1/stream/onchain"}}

503 STREAM_UNAVAILABLE: the on-chain or Polymarket stream is stopped or restarting

{"error":{"code":"STREAM_UNAVAILABLE","message":"the stream server is unavailable"}}

Error frames

An error frame that no close follows leaves the connection open: the rejected message is discarded and the rest continue. Every one has type: "error", code, message, and id when it is about one of your subscribes.

codeWhenWhat the client does
bad_subscriptionThe message or its filter is not valid; the message names the fault: invalid JSON, unknown action 'x', duplicate subscription id 'x', no such subscription (an unsubscribe), filter key 'x' is not supported on hl.fills, filter.min_usd: must be a number, filter.wallet: takes at most 1000 values, cursor is not a stream cursor (take one from a subscribed or event frame).Fix the message. Nothing was subscribed.
unknown_channelNot a channel of this address: a typo, or a channel of another path. The message lists what this address serves: unknown channel 'x'; available: [...].Use one from the list, or connect to the channel's own path.
rate_limitedThe connection already holds the maximum number of subscriptions (at most 100 subscriptions per connection).Unsubscribe one, or put several wallets or coins into one subscription's filter.
cursor_expiredThe cursor is older than the stream keeps.Subscribe without a cursor and read the older part over REST.
cursor_aheadThe cursor is newer than the stream.Send a cursor you took from a frame.
replay_too_largeThe replay is too big for the socket; the frame carries cursor and rest.Subscribe again from the frame's cursor, or narrow the filter.
catchup_unavailableThe replay could not be read (catch-up is unavailable right now; subscribe again); the frame carries cursor and rest.Subscribe again at once with the frame's cursor.

For replay_too_large and catchup_unavailable, cursor is the last position the server had sent you on that subscription (or the cursor you sent, when it had sent none), and the events up to it are valid: start from it. rest is the REST catch-up path, and may be absent.

The command line: vike-data

Install, sign in, and every command group. Open source, on npm.

vike-data is the Data API's command line: the npm package @vike-io/cli (0.17.1), MIT-licensed, Node.js 22.12 or newer. Every data command is one call to the MCP server, so it reaches the same tools with the same key and spends the same hourly budget; vike-data stream holds a WebSocket instead, outside that budget.

  • The key is read from VIKE_API_KEY first and from ~/.vike/config.json second.
  • Output is a table on a terminal and JSON with --json or whenever stdout is piped, so vike-data search btc | jq . needs no flag. A tool that refuses its input exits 1.
  • call reaches every tool by name, including those with no command of their own, and prints the raw result, so an integer above 2^53 stays exact (--args, --args-file or - for stdin, --timeout 60 s by default).
  • stream answers the pings, prints event and gap frames on stdout and its status on stderr, and after any drop resumes from each subscription's last cursor; --cursor starts a run from a frame's cursor, at most 3 days old. It exits 1 without reconnecting on 4410, a rejected key or a refused subscription; a closed pipe ends it with 0.
  • It is not the Trader's command line: vike-cli drives the trading platform (Trader docs).
  • Each product's commands, with real arguments: On-chain, Hyperliquid, HL Cohorts, Polymarket.
npm install -g @vike-io/cli     # Node.js 22.12 or newer
vike-data init                   # paste your key once
vike-data doctor                 # key, endpoint and tools in one check
CommandWhat it does
vike-data initInteractive setup: save your API key and verify connectivity (--api-key skips the prompt)
vike-data login --api-key vk_…Save an API key to ~/.vike/config.json
vike-data logoutRemove the saved API key
vike-data whoamiShow which API key is in use (truncated) and where it came from
vike-data doctorDiagnose the setup, endpoint reachability and tool availability
vike-data schema --prettyPrint every MCP tool with its description and input schema
vike-data token …Token search, transfers, screener and holders · its commands
vike-data wallet …Wallet metrics, discovery and ranking · its commands
vike-data labels <address>The label sources of one wallet · its commands
vike-data hl …Hyperliquid: cohorts, wallets, leaderboards, liquidations, markets, builders, vaults · its commands
vike-data perp …Cross-venue perp funding and spreads; Hyperliquid top traders and share-card lookup · its commands
vike-data candles …Hyperliquid and Polymarket candles · its commands
vike-data polymarket … (pm)Markets, traders, smart money, tape, order book, settlements, archive · its commands
vike-data alerts …Alert rules, channels, webhooks, the event feed (at most 200 active per account) · its commands
vike-data search <q>Search everything by name, symbol, address, ENS name or polymarket.com link · its commands
vike-data stream <path>Live events as NDJSON from wss://stream.vike.io/v1/stream/<path>; reconnects and resumes · its commands
vike-data stream info | check | events | connectionsThe stream registry, a filter check, stored events, your connections · its commands
vike-data call <tool> --args '{…}'Call any MCP tool by name and print its raw result · its commands
vike-data install --claude | --cursor | --copilot | --agents | --allCopy the vike skill where each agent looks for it
VariableEffect
VIKE_API_KEYthe key; wins over the saved config
VIKE_MCP_URLthe MCP endpoint, https://data.vike.io/mcp by default
VIKE_NO_TELEMETRY=1 or DO_NOT_TRACK=1turns off anonymous usage telemetry (also off whenever CI is set)
VIKE_NO_UPDATE_CHECK=1 or NO_UPDATE_NOTIFIER=1turns off the background update check

MCP: the server for AI agents

One URL for Claude, ChatGPT, Cursor and every other MCP client.

https://data.vike.io/mcp is the Vike Data API's Model Context Protocol server: one URL, 83 tools, JSON-RPC 2.0 over HTTP POST. Point an MCP client at it and an agent reads wallets, tokens, Hyperliquid, HL Cohorts, Polymarket and Predict.fun, and manages your alerts, in plain words. It is a remote server: nothing to install, nothing running on your machine. Same data, same key, same hourly budget as REST.

PropertyValue
Endpointhttps://data.vike.io/mcp
TransportStreamable HTTP, POST only
ProtocolJSON-RPC 2.0
MCP version2025-03-26
Server identityserverInfo = vike-data, version 1.0.0
Methodsinitialize, notifications/initialized, tools/list, tools/call; any other is -32601
CredentialOAuth 2.1 (Claude Desktop, ChatGPT) or X-API-KEY; Authentication

tools/list returns the whole catalogue in one response, with no paging. The tools, by family:

FamilyToolsCredits a callNames
On-chain wallets, tokens and transactions200–5token_search, wallet_summary, wallet_discover, token_transfers, wallet_label_provenance, token_holders, wallet_transactions, wallet_compare, wallet_trace, wallet_batch_labels, token_screener, wallet_counterparties, wallet_pnl_history, wallet_token_balances, wallet_ens, wallet_first_funder, wallet_exchange_usage, wallet_check, wallet_positions, transaction_lookup
Hyperliquid181–2hl_perp_top_traders, hl_position_match, hl_wallet, hl_wallet_activity, hl_leaderboard, hl_liquidations, hl_market, hl_builders, hl_vaults, hl_vault, hl_orders, hl_twaps, hl_ledger, hl_leverage_changes, hl_wallet_trades, hl_stats, hl_hype_holders, hl_archive
HL Cohorts21hl_cohort_metrics, hl_segments
Polymarket131–2polymarket_markets, polymarket_market_detail, polymarket_wallet, polymarket_market_trades, polymarket_smart_money, polymarket_tape, polymarket_orderbook, polymarket_market_stats, polymarket_settlement, polymarket_settlements_bulk, polymarket_archive, polymarket_leaderboard, polymarket_chainlink_prices
Predict.fun91predictfun_overview, predictfun_markets, predictfun_market_detail, predictfun_market_book, predictfun_market_trades, predictfun_wallet_trades, predictfun_wallet_positions, predictfun_leaderboard, predictfun_wallet_flows
Alerts (the only tools that write: your own rules and channels)100–3alerts_create, alerts_delete, alerts_edit, alerts_events, alerts_list, alerts_list_channels, alerts_register_webhook, alerts_resend_delivery, alerts_rotate_webhook_secret, alerts_test_webhook
Search and streams50–1search, stream_info, stream_check_filter, stream_events, stream_connections
Candles21hl_ohlcv, polymarket_ohlcv
Perps (venues compared, MCP only)41perp_funding, perp_spreads, perp_open_interest, perp_long_short

alerts_* is the only family that writes anything, and what it writes is your own alert rules and channels. The server holds no keys and signs no transactions: nothing here touches a market, an order or a wallet you control. The vike.io Ask AI chat does not register, rotate or test webhooks: a signing secret must not pass through a chat that stores the conversation.

Vike's other MCP server, the trading platform's vike-cli mcp, is a local process set up apart (Trader docs): in a client's configuration it is a second entry under another name, with command and args instead of url, and the two share no credential, budget or transport.

Connect your client

Pick your client; Claude Desktop and ChatGPT need no key. The concrete steps, and what fails, below.

1. Settings → Connectors → Add custom connector
2. Name: Vike    URL: https://data.vike.io/mcp
3. Leave OAuth Client ID and Secret empty, click Add
4. Log into Vike in the tab that opens, click Allow
5. The Vike tools appear under the hammer icon

MCP · client

Claude Desktop

Credential

OAuth 2.1, no key

Free API Archive Stream MCP

Claude Desktop registers itself with Vike's authorization server and you approve in a browser; nothing is copied or pasted.

1. Settings → Connectors → Add custom connector
2. Name: Vike    URL: https://data.vike.io/mcp
3. Leave OAuth Client ID and Secret empty, click Add
4. Log into Vike in the tab that opens, click Allow
5. The Vike tools appear under the hammer icon

Leaving the client id and secret empty is the point: Claude Desktop calls POST /oauth/register (RFC 7591) and gets its own. If the hammer icon shows no Vike tools, authorization did not complete: remove the connector and add it again, watching for the browser tab. Revoke under Account → API Keys → Connected Apps; OAuth 2.1 has what the server enforces.

Ask: “Search for PEPE token using Vike”

That calls token_search, which costs 0 credits, so a failed attempt costs nothing. If the tools are listed but never used, name one in the prompt: "use the vike token_search tool".

claude mcp add --transport http vike https://data.vike.io/mcp \
  --header "X-API-KEY: $VIKE_API_KEY"
  • --transport http is required: the endpoint is Streamable HTTP, not a local process. Without it Claude Code tries to run the URL as a command.
  • --scope picks local, project or user; check claude mcp list afterwards. A project scope writes a file that may be committed: keep the key in the environment, as above.
  • Restart any session that was already open: sessions read the server list at start.
  • $VIKE_API_KEY that expands to nothing registers an empty header: echo it first.
  • Remove it with claude mcp remove vike; deleting the key under Account → API Keys is what retires it.

Ask: “Search for PEPE token using Vike”

That calls token_search, which costs 0 credits, so a failed attempt costs nothing. If the tools are listed but never used, name one in the prompt: "use the vike token_search tool".

MCP · client

ChatGPT

Credential

OAuth sign-in, or X-API-KEY

Free API Archive Stream MCP

Add a remote MCP server in ChatGPT's connector settings:
  Server URL  https://data.vike.io/mcp
  Name        vike
  Credential  OAuth sign-in, or X-API-KEY: vk_…
  • OAuth, where the ChatGPT surface offers it. ChatGPT finds the authorization server from the 401 discovery header, registers itself and you approve in a browser. The server accepts the two redirect URIs OpenAI documents for connectors, https://chatgpt.com/connector_platform_oauth_redirect and https://chatgpt.com/connector/oauth/{callback_id}; any other is invalid_redirect_uri.
  • An API key, where ChatGPT lets you send a custom header: X-API-KEY: vk_….
  • Which plans and menus offer a custom connector is OpenAI's to say; a surface with neither an OAuth option nor a header field cannot carry either credential.
  • Prove the key outside ChatGPT first with the raw HTTP tools/list call.

Ask: “Search for PEPE token using Vike”

That calls token_search, which costs 0 credits, so a failed attempt costs nothing. If the tools are listed but never used, name one in the prompt: "use the vike token_search tool".

MCP · client

Cursor

Credential

X-API-KEY in mcp.json

Free API Archive Stream MCP

Settings → MCP → Add new server, or edit ~/.cursor/mcp.json (every project) or .cursor/mcp.json in a repository (that project only):

{
  "mcpServers": {
    "vike": {
      "url": "https://data.vike.io/mcp",
      "headers": { "X-API-KEY": "YOUR_KEY" }
    }
  }
}
  • url + headers is the remote shape and the only one that works: a command key would not reach an HTTP service.
  • A per-project file sits in the repository: add it to .gitignore, or keep the server in the global file. If a key does get committed, delete it under Account → API Keys and make a new one: the old key keeps working until it is deleted, and for up to 45 seconds after.
  • Reload Cursor or toggle the server; a healthy vike entry lists the tools. A red entry is almost always the JSON: a trailing comma, or url and headers outside the vike object.
  • Keep the server in one file: two vike entries with different keys spend two budgets.

Ask: “Search for PEPE token using Vike”

That calls token_search, which costs 0 credits, so a failed attempt costs nothing. If the tools are listed but never used, name one in the prompt: "use the vike token_search tool".

gemini mcp add --transport http \
  --header "X-API-KEY: $VIKE_API_KEY" \
  vike https://data.vike.io/mcp
  • Order: options, then the name (vike), then the URL. --transport http is required: the default is stdio, which expects a local command.
  • A header with spaces must be one quoted argument, as above; $VIKE_API_KEY that expands to nothing registers an empty header.
  • Check it with gemini mcp list.

Ask: “Search for PEPE token using Vike”

That calls token_search, which costs 0 credits, so a failed attempt costs nothing. If the tools are listed but never used, name one in the prompt: "use the vike token_search tool".

MCP · client

VS Code (GitHub Copilot)

Credential

X-API-KEY in .vscode/mcp.json

Free API Archive Stream MCP

Create .vscode/mcp.json in the workspace and reload the window:

{
  "servers": {
    "vike": {
      "type": "http",
      "url": "https://data.vike.io/mcp",
      "headers": { "X-API-KEY": "YOUR_KEY" }
    }
  }
}
  • The top-level key is servers (not mcpServers), and each entry carries "type": "http".
  • The file sits in the workspace: add it to .gitignore, or use VS Code's input variables so the key is prompted for. If a key does get committed, delete it under Account → API Keys and make a new one: the old key keeps working until it is deleted, and for up to 45 seconds after.
  • A trailing comma silently disables the whole file; MCP configuration is read at startup.
  • In Copilot Chat's agent mode the tools picker lists the server; its tools may need enabling for the conversation.

Ask: “Search for PEPE token using Vike”

That calls token_search, which costs 0 credits, so a failed attempt costs nothing. If the tools are listed but never used, name one in the prompt: "use the vike token_search tool".

MCP · client

Raw HTTP

Credential

X-API-KEY on every POST

Free API Archive Stream MCP

Everything is POST https://data.vike.io/mcp with Content-Type: application/json and one credential header. Each method is dispatched on its own (initialize is not a gate), so a bare tools/list proves the connection:

curl -s https://data.vike.io/mcp \
  -H "X-API-KEY: $VIKE_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"jsonrpc":"2.0","id":1,"method":"tools/list"}' | head -c 400

A tool call

{
 "jsonrpc": "2.0",
 "id": 3,
 "method": "tools/call",
 "params": {
  "name": "token_search",
  "arguments": {
   "query": "PEPE"
  }
 }
}

The result is {"content": [{"type": "text", "text": "<the tool's JSON>"}], "isError": false}: decode the JSON-RPC response, then result.content[0].text. An empty arguments is a valid call but not "everything": each tool's defaults apply (alerts_list lists active rules only).

FailureHTTPBody
Bad or missing credential401 + WWW-Authenticate: Bearer resource_metadata=…{"error": {"code": -32000, "message": "Invalid or missing API key"}}
Hourly limit429 + Retry-After: 60-32000, Hourly rate limit exceeded, data: {"code": "RATE_LIMITED", "retry_after": 60}
Malformed JSON200-32700 with id: null
Unknown method or tool200-32601, Unknown tool: …
A tool that failed200a result with isError: true and {"error": "…"} as its text

Check the body, not just the status. Only a credential failure and the rate limit change the HTTP status. A script that checks response.ok or only the JSON-RPC error reads a failed tool call as a success: check result.isError too.

The alert tools: 10 tools that write to your account

Make, change and delete alert rules, register a signed webhook, read the feed. Each is also a /v1/alerts route.

Tools for alert rules: rules that fire when a transfer happens on chain. Unlike every other tool on this page, these write to your account — they are the only ones that change state.

Transfer alerts (alert_type transfer, the default) watch eth, bsc, base or hyperevm: a token crossing a USD threshold, or a given wallet sending or receiving. See Watching a wallet. The same tools also make Hyperliquid and Polymarket alerts; their types, cohorts and limits are on the Alerts reference.

What constrains a rule, all of it stated by the tools themselves:

  • One chain per transfer alert. Passing two silently watches only the first, with no error to tell you the second was dropped. Watching both means creating two alerts.
  • Two kinds of threshold on transfers. A USD value (min_value_usd at least, max_value_usd at most) and, for an alert on one token_symbol, a size in whole tokens (min_amount at least). Set together, both must hold.
  • One wallet per alert, as a 20-byte EVM address (0x and 40 hex characters). An ENS name, an entity name ("Binance 14") or a non-EVM address is refused, not resolved.
  • The type is fixed once created. alerts_edit changes a rule's parameters, never its type.
  • Delivery is email, a signed webhook, and the feed that every rule fills. Discord alerts are temporarily off. See Where an alert goes.

Alerts belong to the authenticated user, and every tool enforces ownership: you can only list, edit or delete your own. Both credentials resolve to a user, so an API key and an OAuth token reach the same alerts. The same rules can be made under Account → Alerts, with the same checks, and the two always agree on what a rule means. Every tool here is also a REST route under /v1/alerts, on the same code and at the same price: Alerts API.

Alert types

alert_type picks what a rule watches; each type's parameters, a question to ask, the /account link and the CLI line are on its product's page:

alert_typeProduct page
transferOn-chain
hl_walletHyperliquid
hl_cohort_biasHL Cohorts
hl_cohort_fillHyperliquid
hl_liquidationHyperliquid
hl_liq_proximityHyperliquid
hl_wallet_ordersHyperliquid
pm_wallet_tradePolymarket
pm_market_tradePolymarket
pm_price_movePolymarket
pm_resolutionPolymarket

Hyperliquid alerts

They watch perpetual markets, builder markets such as xyz:CL included. Give a coin as Hyperliquid names it (case is forgiven: kpepe is stored as kPEPE); a spot market (PURR/USDC, @107) is refused, and so is a coin Hyperliquid does not list. Delivery is e-mail, a webhook, or the feed alone (channels empty).

TypeFiresNeedsPolicy
hl_walleton a fill of one wallet, narrowed by actionswalletper_event
hl_cohort_fillon a fill by any wallet in the cohorts you namea cohort, and min_value_usd of 10,000 or moreper_event
hl_liquidationon a liquidationnothing: every filter is optionalper_event
hl_cohort_biaswhen a cohort index of a coin flipstoken_symbol, indexhysteresis
hl_liq_proximitywhen a wallet's position nears its liquidation pricewallethysteresis
hl_wallet_ordersonce a check, listing the orders one wallet placed and the ones that left the bookwallet; min_value_usd of 1,000 or more (default 10,000)per_event
  • hl_wallet: actions picks from open (flat to long or short), close (back to flat), flip (long to short or the reverse in one fill), increase (add is accepted) and reduce; the default is open, close, flip, increase, so reduce is opt-in. The fills one check reads are merged into one event per coin, action and side, with fills their count; min_value_usd is a floor on that merged total, and a fill whose action cannot be worked out is never reported.
  • hl_cohort_fill: pnl_cohorts and size_cohorts together; a wallet must match both lists when you give both. Name at least one cohort, in either list.
  • hl_liquidation: wallet, token_symbol, min_value_usd (the size that was closed) and the two cohort lists, all optional and all required to match together; with none set it reports every liquidation.
  • hl_cohort_bias: index is one of smi, wsi, smart_bias, whale_bias, smi_wsi_gap (each -1 to 1, smi_wsi_gap -2 to 2). threshold (default 0) is the line, band (default 0.1, above 0) the margin on each side, direction (up, down or both, the default) the crossings that notify; | threshold | + band cannot exceed the index's range. The index is high once it reaches threshold + band, low at threshold − band, and nothing changes in between. The first reading only sets the side, and a coin with no position in a cohort leaves the state alone.
  • hl_liq_proximity: fires once for a position when its liq_ratio (0 at entry, 1 at liquidation) reaches threshold (default 0.8, strictly between 0 and 1), and re-arms when the ratio falls below threshold − band (default 0.1, smaller than threshold) or the position closes. A long and a short on one coin are watched apart; a position already past the threshold when the rule starts alerts only after it has re-armed.
  • hl_wallet_orders: the wallet's orders from the order-book snapshots taken every 5 minutes: placed when an order is new in a snapshot, gone when it is missing from one, with outcome filled, partially_filled or canceled from the order's own fills (or unknown when the fills arrive more than 10 minutes late). Each check sends one event whose orders lists every order it found, with the check's totals beside it; nothing is left out. A check of more than 200 orders (a market maker's: over a thousand) comes as several events of 200, part 1 to parts, each carrying the same totals. The e-mail shows the totals and the 20 largest orders; the webhook and the feed carry them all. min_value_usd is the order's size × limit price (a stop at its trigger price); events (placed, gone, default both), side (buy or sell, default both) and token_symbol narrow it. An order placed and gone between two snapshots is never seen. These events are private: only the alert's owner receives them, never the public WebSocket.
  • Cohorts: pnl_cohorts and size_cohorts take the labels of the two ladders (Cohorts), read when the event is checked. A wallet with no label on a ladder you named never matches: one with fewer than 10 closed trades has no PnL cohort, and one with no open position has no size cohort, the usual case right after a liquidation, so a hl_liquidation rule with size_cohorts can miss those.
  • 200 watched wallets: an account can watch up to 200 distinct wallets across its active hl_wallet, hl_wallet_orders and hl_liq_proximity rules. A second rule on a wallet you already watch costs nothing, a paused rule does not count, and the optional wallet of hl_liquidation does not count.
  • An edit checks the whole rule again: the stored fields with the ones you pass laid over them. "" or [] clears an optional field; a field the type does not read is an error. A rule that no longer passes (a coin Hyperliquid delisted) cannot be paused with alerts_edit: delete it.

Polymarket alerts

They watch Polymarket's on-chain trade tape, order books and resolutions. A wallet is the proxy wallet, the address of its wallet page; a market is its condition_id, 0x and 64 hex characters. Delivery is e-mail, a webhook or the feed.

TypeFiresNeedsPolicy
pm_wallet_tradeon every trade of one wallet, optionally in one market and above a USD valuewalletper_event
pm_market_tradeon every trade in one market at or above a USD valuecondition_id, min_value_usd of 1,000 or moreper_event
pm_price_movewhen the market's price moves 0.05 or more within 5 minutescondition_idper_event
pm_resolutionwhen the market resolvescondition_idper_event
  • A trade is one fill of a maker order, as the tape records it: a large order filled against several makers is several trades, each with its own USD value (size × price).
  • A price move follows the market's first outcome (Yes, Up, or the first team): the other outcome mirrors it, so each move is reported once. direction picks up, down or both (the default). A market opened in the last few minutes is not followed yet, and a move spanning two 5-minute windows can be reported twice.
  • A resolution is reported once, with the winning outcome, or none when the payout is split.

How soon a Hyperliquid or Polymarket alert arrives

AlertCheckedArrives
hl_wallet, hl_cohort_fillevery 10 seconds, sooner when new fills are announcedusually within a minute or two of the fill
hl_liquidationevery minuteusually within a minute or two of the liquidation fill
hl_cohort_bias, hl_liq_proximityeach new capture of all positions, about every 12 minutesup to about 20 minutes after the move; a new or edited rule is armed one capture later
hl_wallet_ordersevery minute, against the order-book snapshot taken every 5 minutesup to about 7 minutes after the order is placed or gone (later while the node lags), all of a check's orders in one event; a gone order waits for its fills, usually a minute or two, and comes in the check that decides it
pm_wallet_trade, pm_market_trade, pm_resolutionevery 15 seconds (resolutions every minute)about 5 minutes after the block: the tape waits for confirmations
pm_price_moveevery 30 secondswithin a minute of the move

When a Hyperliquid or Polymarket rule is refused

You asked forThe tool says
a parameter the type does not readindex does not apply to hl_wallet; it applies to hl_cohort_bias
hl_wallet without a walletwallet is required for hl_wallet: pass the 0x + 40 hex address to watch
hl_cohort_fill below the floorhl_cohort_fill needs min_value_usd >= 10,000: without a size floor it would match every fill on Hyperliquid
hl_cohort_fill with no cohortPick at least one PnL or size cohort: a rule without one would alert on every fill above $10,000.
a coin Hyperliquid does not listunknown Hyperliquid perp 'BTCC': use the coin as Hyperliquid names it …; did you mean BTC?
a spot marketsymbol 'PURR/USDC' is a spot market; alerts cover perpetuals only (BTC, ETH, kPEPE, xyz:CL)
an unknown cohort or actionunknown pnl cohort 'x'; valid: 3x smart, …
a bias line the index cannot reachthreshold 0.9 and band 0.2 are unreachable for smi (range -1..1), then what to keep within the range
a band of 0 or lessband must be greater than 0 …: a zero band re-fires on every tick around the threshold
a band that cannot re-armband 0.9 must be below threshold 0.8: the rule could never re-arm; pass a smaller band
a market that is not 0x and 64 hex characterscondition_id '…' is not a Polymarket market id: 0x + 64 hex characters, …
pm_market_trade below the floorpm_market_trade needs min_value_usd >= 1,000: a busy market trades thousands of times a day
a webhook you have not registeredno enabled webhook channel: register one first …, or leave channels empty and read GET /v1/alerts/events
a 201st watched walletYou can watch up to 200 wallets. Remove or pause a rule on another wallet first.
chain, address … on a Hyperliquid or Polymarket alert… apply only to transfer alerts; a Hyperliquid or Polymarket alert has no chain, address, max_value_usd or min_amount (use wallet, symbol, condition_id and min_value_usd)

The website's alert form makes every one of these types (What to watch?). Its edit view changes only a Hyperliquid or Polymarket alert's name, description, tags, e-mail and webhooks (and a followed wallet's coin, floor and actions); change its parameters with alerts_edit, or make a new alert.

Alerts · MCP tool

alerts_list

Cost

0 credits a call

Free API Archive Stream MCP

Lists your alert rules. Active only by default.

ParameterTypeDefaultNotes
include_inactivebooleanfalseInclude paused and soft-deleted alerts

Returns {"alerts": [...]}, one row each with id, label, alert_type, chain, token_symbol, min_value_usd, max_value_usd, min_amount, address_conditions, cooldown_minutes, max_triggers_day, notify_channel_ids, is_active, created_at, policy, hl and pm.

  • address_conditions is a list of {"role": "from" | "to" | "any", "address": "0x…"}, empty when the alert watches no wallet; an alert made on the website can carry both a from and a to address, which is why it is a list.
  • policy says how often the rule notifies: per_event, cooldown or hysteresis (see How often a rule notifies). It is null for a rule that cannot run, for example one whose coin Hyperliquid has delisted.
  • hl and pm are null for a transfer alert; for a Hyperliquid or Polymarket alert they hold its parameters (Alerts reference).

This is also how you get an alert_id — alerts_edit and alerts_delete both need one, and neither accepts a label.

Ask: “List my alerts, including the paused ones.”

Alerts · MCP tool

alerts_list_channels

Cost

0 credits a call

Free API Archive Stream MCP

Your enabled notification channels. No parameters.

Returns {"channels": [...]} with {id, channel_type, is_verified, label} per row. channel_type is email or webhook; a Discord connection is not listed, because Discord alerts are off. A webhook is is_verified: true once a delivery to it got a 2xx answer: an alert, or a test (at registration, or with alerts_test_webhook); webhooks registered before this check existed (early November 2026) show true regardless. An unverified webhook still receives its alerts.

Call it before creating an alert. A transfer alert that names a channel you have not set up is still created, and because it then has no channel of its own its notifications go to your account's email address. A Hyperliquid or Polymarket alert that names a webhook you have not registered is refused, so a rule is never left silent by accident.

Ask: “What notification channels do I have set up?”

Alerts · MCP tool

alerts_create

Cost

3 credits a call

Free API Archive Stream MCP

Creates an alert rule. Below, a transfer alert; the same tool creates the Hyperliquid and Polymarket types, whose parameters are on the Alerts reference.

ParameterTypeDefaultNotes
labelstringrequiredThe alert's user-visible name
alert_typeenumtransfertransfer for an on-chain alert, or a Hyperliquid or Polymarket type. Fixed once created
token_symbolstring—Transfer alerts: the token's ticker in UPPERCASE (USDC, ETH, PEPE)
chainenumethTransfer alerts only: eth, bsc, base or hyperevm, single value. Leave it out for a Hyperliquid alert
min_value_usdnumber—Transfer alerts: fire when the transfer's USD value is at least this
max_value_usdnumber—Transfer alerts only: fire only when the transfer's USD value is at most this
min_amountnumber—Transfer alerts with a token_symbol only: fire when the transfer moves at least this many whole tokens (1000000 USDT, 0.5 ETH), priced or not. With a USD bound as well, both must hold. A token whose decimals we do not know never matches it
addressstring—Transfer alerts only: one wallet to watch, 0x and 40 hex characters
address_roleenumanyTransfer alerts only: any (sent or received), from (it sent) or to (it received); needs address
channelsarray—["email"], ["webhook"] or both (see Where an alert goes). Discord is refused
cooldown_minutesinteger60Token-wide transfer alerts only (no address): the minimum gap between notifications, floor of 1. Every other kind ignores it

The Hyperliquid and Polymarket parameters (wallet, condition_id, actions, pnl_cohorts, size_cohorts, index, threshold, band, direction, side, events) are on the Alerts reference.

Returns {"alert": {...}} — the new row in the shape alerts_list uses, including its id and its policy. A request the checks refuse comes back as {"error": "..."} and nothing is created; see When a rule is refused.

A parameter that the type does not read is an error, never ignored: a rule that quietly lacked the filter you typed would alert on the wrong thing. alert_type is transfer unless you say otherwise, so chain defaults to eth.

Ask: “Alert me on email when more than $5M of USDC moves on Ethereum, at most once an hour.”

Ask: “Tell me every time 0x28C6…1d60 sends more than $1M on Ethereum.”

Alerts · MCP tool

alerts_edit

Cost

2 credits a call

Free API Archive Stream MCP

A partial update: only the fields you pass change.

ParameterTypeDefaultNotes
alert_idintegerrequiredRequired. From alerts_list.
labelstring—Same meaning as in alerts_create
token_symbolstring—Same meaning as in alerts_create
chainstring—Same meaning as in alerts_create
min_value_usdnumber—Same meaning as in alerts_create
max_value_usdnumber—Same meaning as in alerts_create
min_amountnumber—Same meaning as in alerts_create; min_amount: "" or 0 removes the size floor
channelsarray—Same meaning as in alerts_create
cooldown_minutesinteger—Same meaning as in alerts_create
addressstring—Same rules as in alerts_create. address replaces the alert's wallet condition; address: "" removes it; address_role alone moves the existing address to that side
address_roleenum—Same rules as in alerts_create. address replaces the alert's wallet condition; address: "" removes it; address_role alone moves the existing address to that side
is_activeboolean—false pauses the alert without deleting it

An edit that leaves address out keeps the wallet condition as it was, and address without address_role keeps the current side (any when the alert had no wallet). A rejected address changes nothing, including the other fields in the same call.

An edit that changes what a rule matches (token, chain, thresholds, wallet) starts the rule afresh: it reports events from that moment on and never replays earlier ones. The same holds when you turn a paused rule back on. Changing the label, the channels or cooldown_minutes does not restart anything. Editing a Hyperliquid or Polymarket alert: Alerts reference.

Ask: “Pause my USDC alert.”

Ask: “Raise the threshold on alert 41 to $10M.”

Alerts · MCP tool

alerts_delete

Cost

2 credits a call

Free API Archive Stream MCP

Removes an alert.

ParameterTypeDefaultNotes
alert_idintegerrequiredThe alert to delete
hardbooleanfalsetrue removes permanently; the default pauses and keeps history

Returns {"ok": true, "alert_id": ..., "hard": ...} on success. A soft delete is the same state as is_active: false, so an alert deleted this way still appears under alerts_list with include_inactive: true and can be re-enabled with alerts_edit. A hard delete also removes the rule's events from the feed.

Ask: “Delete alert 41 — actually, just pause it.”

Alerts · MCP tool

alerts_register_webhook

Cost

2 credits a call

Free API Archive Stream MCP

Registers an HTTPS webhook as a delivery channel. Vike POSTs signed JSON to the URL when your alerts fire, and once right away: a signed test event, whose result comes back in the answer.

ParameterTypeDefaultNotes
urlstringrequiredWebhook URL, https:// only, reachable from Vike's servers
secretstring—Your own HMAC secret, 16 characters or more. Leave it out and Vike generates one

Returns {"ok": true, "channel_id": ..., "channel_type": "webhook", "url_preview": "...", "signed": true, "test": {...}}, plus secret and secret_note when Vike generated the secret.

  • The secret can be shown again. A generated secret is in this answer, and you can show it again at any time on Account → Alerts → Channels (the Show secret button), so losing it does not require a rotation. alerts_rotate_webhook_secret issues a new one when you want one. A secret you passed yourself is not repeated back in this answer.
  • The test send. Right after the channel is stored, Vike POSTs one test event to the URL, signed and built like a real delivery: alert_id 0, alert_label Webhook test, alert_type and event.kind test, and an event.event_id of test: and 32 hex characters, new for every test. test is {"ok": true, "status": 200} for a 2xx answer, {"ok": false, "status": 401} for any other answer, or {"ok": false, "error": "timeout"} (also network, ssrf_refused, …) when none came. A failing test does not refuse the channel. Because the test is signed with the new secret, a receiver that checks signatures answers 401 or 403 until it has it: install the secret, then call alerts_test_webhook. The whole call, address check and test included, takes at most about 20 seconds.
  • https:// only for a new URL. A webhook registered over http:// before keeps working.
  • On the MCP server and over REST (POST /v1/alerts/channels/webhooks) only. The vike.io Ask AI chat does not register, rotate or test webhooks: a signing secret must not pass through a chat that stores the conversation.

The channel is yours and works for every rule you point at it.

The body

Every rule type sends one envelope: schema (vike.alert.v1), alert_id, alert_label, alert_type, triggered_at and event, the event that matched. A transfer also carries the flat keys the first webhook format had (chain, token, value_usd, from_address, to_address, tx_hash), so an old receiver keeps working. The envelope, a complete example and the shape of every event kind are on Alert feed and webhooks.

Verifying a delivery

The signing is Stripe-style. Each POST carries:

Content-Type: application/json
User-Agent: vike-alerts/1.0
X-Vike-Timestamp: <unix epoch seconds>
X-Vike-Signature: t=<ts>,sig=<hex hmac>[,sig=<hex hmac>]
X-Vike-Event-Id: <event_id>
X-Vike-Attempt: <1 for the first try>
X-Vike-Delivery-Id: <this delivery's id, the same on every try>

Compute HMAC-SHA256(secret, "<t>.<raw body>") with the t= of the header (the same value as X-Vike-Timestamp), hex-encode it, and compare it in constant time against every sig= value: accept the delivery if any of them matches. The header carries one sig= per valid secret: one normally, two for the 24 hours after a secret rotation (the new secret's first). A receiver that compares only one value rejects deliveries during those 24 hours. Check the raw bytes you received, before any JSON parser has touched them. Reject the delivery if the timestamp is more than about five minutes old — that is what stops a captured request being replayed.

Verify a delivery

import hashlib
import hmac
import time

def verify(secret: str, raw_body: bytes, signature_header: str, tolerance_s: int = 300) -> bool:
    """raw_body is the request body bytes exactly as received (do not re-serialise the JSON).
    Hostile input (a non-ASCII sig, \`t=²\`, 5000 digits, a lone surrogate, no header) is a False, never an exception."""
    try:
        parts = [p.split("=", 1) for p in signature_header.split(",") if "=" in p]
        ts = next((v for k, v in parts if k == "t"), "")
        if not ts.isdecimal() or len(ts) > 12 or abs(time.time() - int(ts)) > tolerance_s:   # replay window
            return False
        want = hmac.new(secret.encode(), f"{ts}.".encode() + raw_body, hashlib.sha256).hexdigest().encode()
        return any(hmac.compare_digest(want, v.encode()) for k, v in parts if k == "sig")   # ANY sig= may match
    except (ValueError, TypeError, AttributeError, OverflowError):     # UnicodeError is a ValueError
        return False

Every webhook registered now has a secret. A channel registered earlier without one is signed with an empty key, which proves nothing: give it a secret with alerts_rotate_webhook_secret.

Test webhooks on your own computer

To try a receiver before it is deployed, run it on localhost and expose it with a tunnel such as ngrok or cloudflared. Register the tunnel's https:// address as a webhook with alerts_register_webhook, and deliveries arrive on your machine. A tunnel's address is public for as long as it runs, so when you are done remove the channel under Account → Alerts → Channels, or rotate its secret.

Or skip the tunnel and send yourself the events you already have. This script reads your own alert feed, GET /v1/alerts/events, and POSTs each event to a local URL with the body and the headers of a real delivery, signed with your webhook's secret, so the verifier above accepts it. Set VIKE_API_KEY and VIKE_WEBHOOK_SECRET; TARGET_URL is http://localhost:8000/webhook unless you set it.

Forward your alerts to localhost

import hashlib
import hmac
import json
import os
import sys
import time
from datetime import datetime

import requests

API = os.environ.get("VIKE_API_BASE", "https://data.vike.io")
KEY, SECRET = os.environ["VIKE_API_KEY"], os.environ["VIKE_WEBHOOK_SECRET"]
TARGET = os.environ.get("TARGET_URL", "http://localhost:8000/webhook")
STATE, EVERY = "vike_forward.cursor", 5       # the cursor file; seconds between polls (720 requests an hour)

def delivery(row):
    """The body and the headers a real webhook delivery of this feed row carries."""
    ev = row["event"]
    body = {"schema": "vike.alert.v1", "alert_id": row["rule_id"], "alert_type": row["alert_type"], "event": ev,
            "alert_label": row["alert_label"] or f"alert #{row['rule_id']}",
            "triggered_at": datetime.fromisoformat(ev["ts"].replace("Z", "+00:00")).isoformat()}
    if ev["kind"] == "transfer":              # the flat keys of the earlier webhook format
        who, data = ev["subject"], ev["data"]
        bare = lambda v: v.lower().removeprefix("0x") if isinstance(v, str) else v
        usd = data.get("value_usd")
        body.update(chain=ev["chain"], token=who.get("token_symbol") or "", value_usd=None if usd is None else float(usd),
                    from_address=bare(who.get("from")), to_address=bare(who.get("to")),
                    tx_hash=bare(who.get("tx_hash") or data.get("tx_hash")))
    raw = json.dumps(body, sort_keys=True, separators=(",", ":")).encode()
    ts = int(time.time())
    sig = hmac.new(SECRET.encode(), f"{ts}.".encode() + raw, hashlib.sha256).hexdigest()
    hook = next((d["id"] for d in row["delivery"] if d["channel"] == "webhook"), 0)
    return raw, {"Content-Type": "application/json", "User-Agent": "vike-alerts/1.0", "X-Vike-Timestamp": str(ts),
                 "X-Vike-Signature": f"t={ts},sig={sig}", "X-Vike-Event-Id": ev["event_id"],
                 "X-Vike-Attempt": "1", "X-Vike-Delivery-Id": str(hook)}

cursor = open(STATE).read().strip() if os.path.exists(STATE) else ""
sent = set()
while True:
    try:
        more = True
        while more:                           # a page that says there is more is read at once
            r = requests.get(f"{API}/v1/alerts/events", headers={"X-API-Key": KEY},
                             params={"cursor": cursor, "limit": 500}, timeout=20)
            r.raise_for_status()
            page = r.json()
            for row in page["events"]:
                if row["id"] not in sent:
                    raw, headers = delivery(row)
                    status = requests.post(TARGET, data=raw, headers=headers, timeout=20).status_code
                    sent.add(row["id"])
                    print(row["event"]["event_id"], status, flush=True)
            cursor, more = page["next_cursor"], page["has_more"]
            open(STATE, "w").write(cursor)
    except (requests.RequestException, KeyError, ValueError) as e:
        print("error", type(e).__name__, file=sys.stderr, flush=True)    # the next poll tries again
    time.sleep(EVERY)
  • Steady and inside the limits. It asks the feed every 5 seconds: 720 requests an hour and 12 a minute, against the 20,000 an hour and 200 a minute the API publishes. Each call is one credit.
  • It keeps its cursor in vike_forward.cursor, so a restart continues where it stopped. The first run forwards everything the feed still holds, up to 15 days; delete the file to forward it all again. Within one run nothing is forwarded twice. After a restart the page it was on can be sent again, which a receiver that dedupes on alert_id and event.event_id (see below) drops.
  • It prints each forwarded event's id and the status your receiver answered, and nothing else; a failed call prints only the error's class name. The key and the secret are read from the environment and never printed.
  • Two headers are filled in as best they can be. X-Vike-Delivery-Id is the id of the event's webhook delivery in the feed (0 when the rule has no webhook channel), and X-Vike-Attempt is always 1.

Retries and duplicates

  • Answer with any 2xx within 20 seconds. That is one budget for the whole try: the address lookup, the connection, TLS, the request and your response's status line. After it the try is a timeout. A redirect is not followed and counts as a failure.
  • One delivery at a time per webhook. Vike never has two POSTs open to the same channel, and a slow or hanging endpoint delays only its own deliveries, never another channel's.
  • A failed delivery is tried again after 30 seconds, then after twice as long each time, up to an hour between tries, and given up on after 72 hours. A webhook that keeps failing is paused for a growing interval, at most 5 minutes, so one dead URL does not slow your other channels, and after three failures in a row Vike emails you once a day about it.
  • A 429 with a Retry-After header (seconds or an HTTP date) pauses that webhook for at least that long; the next try is never sooner, and never later than the 72 hours.
  • Delivery is at least once. A send that was cut off can arrive twice, so dedupe on the rule and the event: alert_id and event.event_id from the body (one event can match two of your rules, and each sends its own delivery). That pair is the replay-safe key, because the body is what the signature covers. X-Vike-Delivery-Id identifies the delivery too, but no header is signed: a captured request replayed inside the 5-minute window with a changed header still verifies, so dedupe on the header only alongside the body pair. A delivery sent again with alerts_resend_delivery keeps its X-Vike-Delivery-Id and counts on in X-Vike-Attempt. Events are not guaranteed to arrive in the order they happened.
  • Every delivery to a webhook, with its status, tries, last error and a Send again button, is listed under Account → Alerts → Webhook log for 15 days.

Ask: “Send my alerts to https://example.com/hooks/vike as well, with a signing secret.”

Alerts · MCP tool

alerts_rotate_webhook_secret

Cost

2 credits a call

Free API Archive Stream MCP

Issues a new signing secret for one of your webhook channels.

ParameterTypeDefaultNotes
channel_idintegerrequiredThe webhook channel, its id from alerts_list_channels

Returns {"ok": true, "channel_id": ..., "secret": "...", "secret_note": "...", "previous_secret_valid_until": "..."}. The new secret is in this answer, like a generated one at registration, and can be shown again on Account → Alerts → Channels (Show secret).

For 24 hours after a rotation the old secret keeps signing as well: every delivery carries two sig= values, the new secret's first, until previous_secret_valid_until. A receiver that accepts a delivery when any sig= matches (the verifier above) keeps working while you swap the secret; one that compares a single value rejects deliveries for those 24 hours. Rotating again inside them ends the first overlap. A channel that had no secret rotates without an overlap (previous_secret_valid_until is null, one sig=). Install the new secret, then check it with alerts_test_webhook.

Another user's channel, an email channel and an id that does not exist all answer webhook channel not found.

Ask: “Rotate the signing secret of my webhook.”

Alerts · MCP tool

alerts_test_webhook

Cost

1 credit a call

Free API Archive Stream MCP

Sends the signed test event to one of your webhook channels again — for example after your receiver installed a new secret.

ParameterTypeDefaultNotes
channel_idintegerrequiredThe webhook channel, its id from alerts_list_channels

Returns {"ok": true, "channel_id": ..., "test": {...}}, with test as at registration: {"ok", "status"} when the endpoint answered (ok for a 2xx), else {"ok": false, "error": ...}. The event is signed like a real delivery, with the current secret and, inside a rotation's 24 hours, the previous one too. Its X-Vike-Delivery-Id is its test: event id, new for every test. Nothing is queued, retried or stored. The same webhook channel not found as for a rotation.

Ask: “Send a test event to my webhook.”

Alerts · MCP tool

alerts_events

Cost

1 credit a call

Free API Archive Stream MCP

Reads the events your alert rules have produced, oldest first. To follow the feed, pass the returned next_cursor back as cursor.

ParameterTypeDefaultNotes
cursorstring—The previous page's next_cursor, passed back unchanged. Omit it for the oldest retained event
limitinteger100Events per page, 1 to 500
rule_idinteger—Only the events of this alert (its id from alerts_list). It must be a positive id; anything else is an error rather than a read of every rule

Returns {"events": [...], "next_cursor": "...", "has_more": false}. Each event carries id, rule_id, alert_label, alert_type, created_at, event (what happened) and delivery: one item per channel it went to, with the delivery's id (what alerts_resend_delivery takes), channel, channel_key, status, attempts, last_attempt_at, next_attempt_at (while it waits) and last_error as a code: http 500, timeout, network, tls, blocked, expired or error. An empty page returns the same cursor, so a poller keeps it and asks again. An event is in the feed as soon as it is recorded, before its email or webhook goes out. Events and their deliveries are kept for 15 days, and only your own alerts appear.

It is the same feed as GET /v1/alerts/events; the fields, the paging rules and every event shape are on Alert feed and webhooks.

Ask: “Show me what my alerts have fired since yesterday.”

Alerts · MCP tool

alerts_resend_delivery

Cost

1 credit a call

Free API Archive Stream MCP

Sends one of your deliveries again: the webhook or email of an event that failed, expired, or was lost on your side.

ParameterTypeDefaultNotes
delivery_idintegerrequiredThe delivery's id, from an event's delivery list in alerts_events

Returns {"id": ..., "status": "retry", "result": "queued"}. result is queued (it will be tried at once), already_queued (it was already waiting for a try; nothing changed) or sending (a try is under way right now).

  • Any status, up to 15 days after the event. A delivery that had already closed (sent, expired, skipped) gets a fresh 72 hours of retries.
  • It goes out with the same event, the same X-Vike-Delivery-Id, a new signature and the next X-Vike-Attempt, and is then retried like any delivery.
  • queued is a request, not a promise: the sender checks the rule and the channel again, so a delivery whose rule is paused or whose channel is disabled or deleted is skipped again, and a paused webhook delays the try.
  • An error, and no credit, for a delivery that is not yours or does not exist (the same answer for both), one whose event is older than 15 days (and, once the prune removed it, the same "does not exist"), and an unsupported one (its channel cannot carry that kind of event, so it would fail the same way).
  • Over REST it is POST /v1/alerts/deliveries/{id}/resend, and on the website the Send again button of Account → Alerts → Webhook log.

Ask: “The webhook of my last USDC alert never arrived: send it again.”

Alerts

How often a rule notifies

Free API Archive Stream MCP

policy in alerts_list is fixed by the rule's type. A transfer alert is one of two:

policyRulesWhat it does
per_eventa transfer alert with an addressOne notification for every matching event. No cooldown and no daily cap; cooldown_minutes is ignored. A busy wallet or a low floor is a flood, so set a floor
cooldowna transfer alert without an address (token-wide)At most one notification per cooldown_minutes (default 60) and 10 a day, naming the newest matching transfer

Every event, whatever the policy, is kept in the feed for 15 days. An account can have up to 200 active alerts in all, of any kinds; the 201st is refused with "You can have up to 200 active alerts. Pause or delete one first.", when you create it and when you turn a paused one back on.

Alerts

Watching a wallet

Free API Archive Stream MCP

address and address_role ask for a transfer that involves one wallet: from matches a transfer the wallet sent, to one it received, any either. Stored on the alert exactly as the website's alert form stores it (from_address, to_address or any_address in the alert's notes), so an alert made here opens and edits correctly under Account → Alerts, and the reverse.

What the match is, and is not:

  • A transfer row, any token. The alert compares the address with the sender and receiver of each recorded transfer on the alert's chain: native coin and token transfers alike. With no token_symbol every token counts, spam and dust tokens included.
  • Every match notifies. An alert with an address is per_event: one notification for each matching transfer, with no cooldown and no daily cap, so a busy wallet with no threshold is a flood that nothing throttles.
  • min_value_usd is optional, and it changes what matches. With it, only transfers that already carry a USD value at or above it match, so an unpriced token never does. Without it, every transfer touching the address matches, priced or not. max_value_usd is an upper bound on the same value and likewise needs a priced transfer. min_amount (with a token_symbol) is a floor in whole tokens that needs no price.
  • Not supported. An ENS name or an entity label as the address; a non-EVM address; two wallets on one alert; "any wallet in this group". Create one alert per wallet and per chain.
  • Not the same as a wallet's balance or PnL. The alert looks at individual transfers; it does not know a wallet's net position.

A token-wide transfer alert (no address) is the cooldown policy above: one notification per cooldown, for the newest matching transfer, at most 10 a day.

Alerts

When a rule is refused

Free API Archive Stream MCP

A request the checks refuse returns {"error": "..."} in the tool result (the website form shows the same text in a red notice), nothing is created or changed, and the call is not billed. Each message says what to change.

You asked forThe tool says
an address that is not a 20-byte EVM addressaddress '…' is not a 20-byte EVM address: pass '0x' + 40 hex characters. ENS names, entity names and non-EVM addresses (Solana, Bitcoin) are not supported.
a token the transfer alert's chain does not have (its native coin, ETH on eth, is accepted)No token BTC on eth: an alert on it could never fire. Did you mean WBTC?
Discord on any alertDiscord alerts are temporarily off …
a 201st active alertYou can have up to 200 active alerts. Pause or delete one first.
wallet, index … on a transfer alert… apply only to Hyperliquid and Polymarket alerts …; this is a transfer alert

The website's alert form creates these alerts too (What to watch? at the top of the form). The Hyperliquid and Polymarket types, their checks and their messages are on the Alerts reference.

The search tool is the vike.io search box, the same answer as GET /v1/search, at 1 credit. The other four help an agent use the WebSocket, which it cannot hold open itself: describe the stream, check a subscription before a program sends it, read what a channel carried, and see your own connections. Each also has a CLI command: vike-data stream info, check, events, connections.

Streams · MCP tool

stream_info

Cost

0 credits a call

Free API Archive Stream MCP

How to use the vike WebSocket stream (wss://stream.vike.io/v1/stream/<path>), read off the server's own registry: every path (hl, hl-cohorts, onchain, polymarket) with its channels; per channel its filter keys, each a list (with its maximum length), one value, a number (min_usd and the other min_ keys) or a boolean (contract: true or false, not a string), with the allowed values or the server's own message for a bad one, and its defaults (hl.fills: min_usd 1000 unless given); the REST catch-up route of each path; the limits (ONE connection per API key per path and THREE per account per path, newest wins with close 4410; 100 subscriptions per connection; a ping every 15 s); the close codes; cursor retention (3 days) and the subscribe message's shape. Check a filter with stream_check_filter and read missed events with stream_events.

No parameters.

Streams · MCP tool

stream_check_filter

Cost

0 credits a call

Free API Archive Stream MCP

Validate a stream subscribe message (or a channel + filter) with the SAME validator the stream server runs, without connecting. Returns {valid: true, url, subscribe} with the filter exactly as the server will apply it (values normalised, lists sorted, defaults added: hl.fills gets min_usd 1000 unless given), or {valid: false, code, message} with the server's own error (bad_subscription naming the key, unknown_channel listing the path's channels). path checks the channel against the endpoint the connection would use (a channel of another path is unknown_channel there). Costs nothing.

ParameterTypeDefaultNotes
messageobject or string—A whole subscribe message, e.g. {"action": "subscribe", "id": "w1", "channel": "hl.fills", "filter": {"coin": ["BTC"]}}. Or give channel and filter instead.
channelstring—A stream channel, e.g. hl.fills, hl.liquidations, chain.transfers, pm.trades; stream_info lists them all. Used when message is not given.
filterobject or string—The filter object, e.g. {"wallet": ["0x..."], "min_usd": 50000}. Used when message is not given.
pathstring: hl, hl-cohorts, onchain, polymarket—The endpoint the connection would use. Default: the channel's own path.

Streams · MCP tool

stream_events

Cost

1 credit a call

Free API Archive Stream MCP

Stored events of ONE stream channel after a cursor, oldest first, with the stream's own filters: the REST catch-up of wss://stream.vike.io, for what a socket missed or a quick look without a socket. Returns {events: [{cursor, event}], next_cursor, has_more}; event is byte for byte what the WebSocket frame carried. An idle window of the on-demand channel is an item {cursor, gap} in its place. Pass next_cursor back as cursor for the next page; a page at the end returns the channel head, so an empty page on a quiet channel returns the same cursor. Events are kept 3 days: an older cursor is refused with a resume cursor in the message. since_ms starts at an ARRIVAL time instead of a cursor. Delivery is at least once: dedupe on event.event_id. hl.fills without min_usd is min_usd 1000, as on the socket (0 = every fill). Every channel stream_info lists is accepted, including one the stream does not run yet (a subscribe there answers unknown_channel): its pages are empty, like a quiet channel's. Same data as GET /v1/{hyperliquid,onchain,polymarket}/stream-events.

ParameterTypeDefaultNotes
channelstringrequiredA stream channel, e.g. hl.fills, hl.liquidations, chain.transfers, pm.trades; stream_info lists them all. Required.
cursorstring—next_cursor of the previous page or the cursor of a stream frame. Omit (and omit since_ms) for the oldest retained event.
since_msinteger or string—Epoch MILLISECONDS: start after this arrival time instead of a cursor. Not together with cursor.
filterobject or string—The channel's filter keys as on a subscribe, e.g. {"coin": ["BTC"], "min_usd": 100000}. An unknown or channel-foreign key is refused.
limitinteger or string200Events per page, 1 to 1000 (default 200).

Streams · MCP tool

stream_connections

Cost

0 credits a call

Free API Archive Stream MCP

Your own recent stream connections (this account only, every API key and OAuth app of it), newest first: path, url, api_key_id (or oauth_client_id), connected_at, state open or closed, the subscribe count and, once closed, closed_at, duration_s, the channels, close_code (4410 = replaced by a newer connection) and events_sent. RECONSTRUCTED from the connect, subscribe and close records the stream writes, not read from the stream's live state, and it may lag a few seconds: open means no close has been recorded yet (a server crash records none), and an open connection's channels are named only when it closes. Use it to see why a socket was closed or whether two programs share a key (each closes the other with 4410). truncated is true when only the newest records of the window could be read.

ParameterTypeDefaultNotes
hoursinteger or string168How far back to look, 1 to 720 hours (default 168, a week). A connection opened before it is not listed.
limitinteger or string50Connections to return, newest first, 1 to 1000 (default 50).

Candles

Hyperliquid markets and Polymarket outcome tokens.

Two candle tools, one per venue whose data is on chain. Neither takes a contract address: there are no candles for Ethereum, BNB Chain, Base or HyperEVM tokens, and none for centralised-exchange tickers. Asked to chart a token, an agent says so rather than substituting another series.

Candles · MCP tool

hl_ohlcv

Cost

1 credit a call

Free API Archive Stream MCP

Historical OHLCV candles for a Hyperliquid market, aggregated from HL's own executed fills, by view. candles (default): one symbol's bars. Pass symbol as the HL asset (BTC, ETH, HYPE); market picks perps or HL spot and dex picks a builder-deployed HIP-3 perp DEX (default HL, the core venue). Buckets are left-aligned UTC and the window is half-open [start, end). Returns candles as {ts (epoch ms, UTC), open, high, low, close, volume, volume_usd, n_trades} ascending by ts, plus source naming the rollup that answered. export: the same, with limit defaulting to the maximum, 5000 (the REST export streams it as NDJSON). latest: the newest 1-minute bar of EVERY symbol that traded in the last hour in one call (takes only market, dex and interval; dex omitted = every DEX, each row naming its own; a bar may still be forming). Same data as GET /v1/hyperliquid/ohlcv, /ohlcv/export and /ohlcv/latest.

ParameterTypeDefaultNotes
viewstring: candles, latest, export"candles"candles (default): one symbol's bars; export: the same with limit defaulting to 5000; latest: the newest 1-minute bar of every symbol.
symbolstring—HL asset: perp ticker (BTC), spot token (HYPE) or raw spot index (@107). Required by candles and export.
intervalstring: 1m, 5m, 15m, 30m, 1h, 4h, 1d—Candle width. Default 1h; latest takes 1m only, its default.
marketstring: perp, spot"perp"Perpetual or HL spot. Default perp.
dexstring—Perp DEX. Default HL, the core venue; the others are builder-deployed HIP-3 DEXs. On latest, omitted = every DEX.
startstring—ISO-8601 UTC or epoch ms (inclusive). Default: end - limit*interval.
endstring—ISO-8601 UTC or epoch ms (exclusive). Default: now.
limitinteger or string—Max candles (default 500 on candles, 5000 on export; hard max 5000).

Built from Hyperliquid's own executed fills. The route, a builder and the bars: Hyperliquid AI & API, /v1/hyperliquid/ohlcv and historical candles; closed bars live on the hl.candles channel.

Candles · MCP tool

polymarket_ohlcv

Cost

1 credit a call

Free API Archive Stream MCP

Historical OHLCV candles for ONE Polymarket outcome token, from the recorded L2 tape. token_id is the ERC-1155 outcome token id — a market's condition_id will NOT work here; read the token ids off clob_token_ids from polymarket_market_detail. Prices are probabilities in [0, 1]. Returns bars OLDEST first as {ts (epoch ms, UTC), open, high, low, close, volume, trades}. There is no cursor, so a window wider than limit is truncated at the OLD end — raise from to reach the recent end. With to and no from, limit keeps the bars nearest to (to =X, limit =1 is the last bar at or before X).

ParameterTypeDefaultNotes
token_idstringrequiredERC-1155 outcome token id. Required.
intervalstring: 1m, 5m, 1h, 1drequiredCandle width. Required.
frominteger or string—Epoch MILLISECONDS, inclusive lower bound. Omit for no lower bound.
tointeger or string—Epoch MILLISECONDS, INCLUSIVE upper bound. Omit for no upper bound.
limitinteger or string500Max candles (default 500, hard max 1000).

One outcome token, priced in probability (0 to 1). The route: /v1/polymarket/ohlcv.

Perps: venues compared

Funding, spreads, open interest and long/short across perpetual-futures venues.

MCP and chat only. These four compare perpetual-futures venues: a funding rate, a price, how much is open or how a venue's traders lean, per exchange, Hyperliquid among them. They have no REST route: centralised-exchange market data is not part of the REST API. They answer where and how much, never who; who is on Hyperliquid, which settles on chain, is on Hyperliquid and HL Cohorts.

perp_funding and perp_spreads take symbol and nothing else; the venue comes back in the answer. perp_open_interest and perp_long_short take an optional exchange. From a terminal: vike-data perp funding, vike-data perp spreads, and vike-data call for the other two.

Perps · MCP tool

perp_funding

Cost

1 credit a call

Free API Archive Stream MCP

Perpetual-futures funding rates. Pass symbol (e.g. 'BTC') to get current per-venue rates for one asset. Omit symbol to get top cross-venue funding-arbitrage opportunities (pre-ranked, normalised to daily %, spreads >= 0.3%). Each arb row includes long_exchange+long_daily_pct, short_exchange+short_daily_pct, spread_daily_pct. A halted or no-longer-polled venue (Coinbase International, halted 2026-10-01) is left out, and so is a venue silent for over an hour in the per-symbol list.

ParameterTypeDefaultNotes
symbolstring—Specific symbol (e.g. 'BTC'). Omit for all symbols.

Perps · MCP tool

perp_spreads

Cost

1 credit a call

Free API Archive Stream MCP

Get top cross-venue spread-arbitrage opportunities for perpetual futures. Returns symbols where the price gap between the cheapest and most-expensive exchange is >= 0.5%. Each row includes buy_exchange+buy_price, sell_exchange+sell_price, and spread_pct. Pre-ranked — just cite the rows directly. Outliers (>400 bps from cross-venue median, typically unit-mismatch listings like 1000SHIB) are already filtered out.

ParameterTypeDefaultNotes
symbolstring—Symbol to compare. Omit for full matrix.

Perps · MCP tool

perp_open_interest

Cost

1 credit a call

Free API Archive Stream MCP

Open interest of one perpetual-futures symbol on each venue Vike polls: aster, binance, bitget, bybit, hyperliquid, kucoin, okx. oi_usd is USD NOTIONAL (open contracts x mark price when polled), not contracts or coins. Without hours: the newest print per venue and its age in seconds; venues printed within the last hour, stale_venues went silent (last print within 30 days) and must NOT be summed in. With hours (1-168): an hourly series per venue, the last print in each UTC hour. Venues are polled roughly every 5-8 minutes. symbol is the canonical base asset ('BTC', not 'BTCUSDT'). A symbol no venue lists returns empty lists and a note, never zeros.

ParameterTypeDefaultNotes
symbolstringrequiredRequired. Base asset, e.g. 'BTC', 'ETH', 'SOL' (not 'BTCUSDT').
exchangestring—Only this venue (lower case), e.g. 'binance'. Omit for every venue.
hoursinteger or string—Omit for the latest print per venue. 1 to 168 for an hourly series over that many hours.

Perps · MCP tool

perp_long_short

Cost

1 credit a call

Free API Archive Stream MCP

Long/short positioning that perp venues publish for one symbol: binance, bybit, gate, okx. The venues do NOT measure the same thing, so every row carries basis: binance = top traders by POSITIONS; okx and gate = top traders by ACCOUNTS; bybit = ALL accounts. Compare a venue with itself over time, never as a like-for-like average across venues. long_share / short_share are fractions of 1 (0.63 = 63 %); long_short_ratio = long / short (1.72 = 1.72 longs per short; above 1 is long-heavy). Without hours: the newest 5-minute print per venue and its age in seconds (venues printed within the last hour, stale_venues went silent). With hours (1-168): an hourly long_short_ratio series per venue, the last print in each UTC hour. Covers about 40 major symbols only; any other symbol returns empty lists and a note, never zeros. symbol is the canonical base asset ('BTC').

ParameterTypeDefaultNotes
symbolstringrequiredRequired. Base asset, e.g. 'BTC', 'ETH', 'SOL' (not 'BTCUSDT').
exchangestring—Only this venue (lower case), e.g. 'okx'. Omit for every venue.
hoursinteger or string—Omit for the latest print per venue. 1 to 168 for an hourly series over that many hours.

Glossary

The words these pages use in a specific sense.

applied cap

On /v1/onchain routes, the most rows a route returns, which can sit below the limit it accepts: a limit above the cap but within the accepted maximum is silently cut to the cap. Each parameter list names it. The namespace does not page, so a page as long as the cap may not be the whole answer; see Page through large reads.

axis

The dimension a cohort matrix groups by. On /symbols/{symbol}/cohort-metrics it is one of size, pnl, pnl_abs, tier or pnl_unrealized; each axis has its own ladder of cohort labels.

builder

A Hyperliquid builder code: the address an order was routed through, which earns a fee on the fill (builder_fee). The /builders routes rank builders by fee revenue, routed volume, unique traders and fills.

cohort

A labelled bucket on an axis: wallets grouped by account value or PnL rung, or, on the tier axis, positions grouped by their own notional. /cohorts publishes the exact label strings ?cohort= accepts. Not the same as the Polymarket smart money cohort.

condition_id

A 0x -prefixed hex string identifying one Polymarket market. It is what /markets/{condition_id} and /settlements/{condition_id} take in the path. Not interchangeable with a token_id: a token_id passed where a condition_id belongs answers 200 with an empty array.

credit

The accounting figure recorded on every usage row (credits_used). A REST call logs one flat default; an MCP tool has its own price. Credits are never compared against a balance, and a rate-limited or rejected call logs zero. The hourly limit counts requests, not credits. The table is on Limits and credits.

current

The default label basis on axis=pnl: the cohort is resolved at query time, so a historical row carries today's grading. Right for "who is smart right now", and lookahead for a backtest.

freshness

An object on cohort-metrics responses whose window reaches the present: {newest_ts, behind_hours, settled_lag_ceiling_hours, stale}. stale is the precomputed verdict, so a client need not re-derive the rollup's cadence. A window ending in the past carries none.

grading

How wallets are ranked into PnL cohorts on axis=pnl / axis=pnl_unrealized: realized (cumulative realized PnL), realized-pit (the same ladder graded point in time across the whole window) or unrealized (unrealized PnL of open positions - who is onside now).

HIP-3 dex

A builder-deployed perpetuals exchange on Hyperliquid, selected with dex (for example XYZ) and distinct from Hyperliquid core, which is HL. New ones are added over time, so do not hard-code a list.

HL Cohorts

The product, and the /v1/hl-cohorts namespace, for Hyperliquid positioning by cohort: the cohort ladders and segments, the coin by cohort heatmap and per-coin cohort series. It reads the same Hyperliquid data as /v1/hyperliquid, and the same paths under /v1/hyperliquid still answer. Reference: HL Cohorts.

hourly limit

One rolling-hour request budget per credential, shared by /mcp and /v1 and not spent by archive downloads. It counts requests, not credits. Over it you get 429 with Retry-After: 60. The number is on Rate limits.

keyset cursor

The pagination token a Hyperliquid or HL Cohorts list route returns as nextCursor. Pass it back verbatim as cursor; do not construct one. It resumes after the last row's sort key instead of skipping rows, so a deep page costs no more than the first and a concurrent insert cannot shift rows across a page boundary. On cohort-metrics it pages over hours, not rows. Polymarket's /events and /settlements/bulk spell theirs next_cursor, inside an envelope with has_more; see Page through large reads.

L1

Level 1: the derived top of the order book - best bid, best ask and both sizes, one row per change. Published as the l1_quotes archive stream, derived from L2 and far smaller. Archive only.

L2

Level 2: the full order-book event tape - book snapshots, price_change deltas, trades, status markers and, from November 2026, tick-size markers (earlier days have none). Served by /events and /orderbook and published as the book_events archive stream.

label basis

Which grading a cohort label comes from, set with labelBasis: current or point_in_time. Responses declare it in label_basis, and label_computed says when the label was computed.

lookahead

Using information that did not exist yet. Here: a historical cohort row labelled with a grading computed later, so a backtest is told which wallets eventually became rankable. label_computed.lookahead states which you got; use point_in_time for backtests.

manifest

manifest.json: the open index of every Polymarket archive partition - its streams, schema, row counts and bytes. It needs no API key, so read it before downloading anything. The Hyperliquid files are indexed by /v1/hyperliquid/archive/datasets instead.

MCP tool

A function an MCP client calls on data.vike.io/mcp, listed by tools/list and priced in credits. REST routes and tools share one hourly limit. Reference: AI data tools.

partition

One UTC day of one archive stream, venue=polymarket/date=YYYY-MM-DD/{stream}.parquet, with a second layout grouped by asset and tenor. A partition is downloaded as a whole file; see the bulk archive guide.

point_in_time

The label basis that returns each row's cohort label as it was that hour, so a backtest sees no lookahead. On pnl, every field is present while each hour of the window carries its stored label (every hour from 2026-08-06 09:00 UTC, and most earlier ones); a window reaching an hour without one (all of April 2026, about 365 hours from May to 2026-08-06 08:00) comes back with five fields null, named in unavailable_fields. size, tier and pnl_unrealized are always point in time. openedWithin is refused (400) with pnl on this basis.

provenance

Which label source claimed what about a wallet, beside the consensus label built from them. /labels/{address}/provenance returns it for Ethereum addresses, and answers with fewer sources while one of them is being refreshed. See Wallet labels.

provisional

A flag on /symbols/{symbol}/metrics rows, true on at most one of them: the newest, still-unsettled hour. Drop it before persisting a series. /cohort-metrics carries none, because a partial hour inside a matrix fakes a move at the edge.

proxy wallet

The 0x address a Polymarket trader's activity is recorded under on the on-chain trade tape. It is what /wallets/{address} takes and what the response echoes as proxy_wallet.

recorded market

A Polymarket market in the set whose order book we record, the set /markets lists. /markets/{condition_id} also answers for any other market from Polymarket's full catalog, and every row says which with source: recorded or catalog. A catalog row has tick_size and neg_risk null, and no recorded order book.

settlement

The on-chain resolution of a Polymarket market: which outcome won, the payouts vector (in outcome order) and the block and timestamp it resolved at. The authority on how a market ended; the directory's closed flag is derived from it. See /settlements/{condition_id}.

smart money

On Polymarket, the cohort of wallets with at least $1,000 of realized P&L, at least five resolved markets and at least $5,000 of volume. /smart_money reports what that cohort is doing.

symbol

The one name for an instrument on every /v1/hyperliquid route: the symbol= parameter, the /symbols/{symbol}/... paths and the symbol field of every row. It is Hyperliquid's own identifier — BTC, a HIP-3 dex market such as xyz:TSLA, or a spot pair's index such as @107. Until 2026-11-19 responses also carry the old field names (asset, coin, by_coin and the rest).

token_id

A long decimal string identifying one outcome token of a Polymarket market; a binary market has two, returned as token_ids. The per-token routes (/market_stats, /ohlcv, /orderbook, /events) take it. Not a condition_id.

What the API does not cover

Listed plainly, so a person or an AI assistant does not have to guess.

The API answers from the data it holds. These questions need data it does not hold, so no route, tool or file answers them today.

AreaNot available
Prices and marketsA token's price by contract, its candles, biggest movers, all-time high or moving averages on Ethereum, BNB Chain, Base or HyperEVM (the USD value of what a wallet holds is in its balances); DEX pools, swaps and new pool launches; centralised-exchange candles, trades and order books over REST; prediction markets other than Polymarket and Predict.fun
Wallets and chainsToken approvals, cost basis and tax lots; DeFi and lending positions (balances list tokens only); funds followed across a bridge (a trace follows one chain); Solana and Bitcoin
HyperliquidRejected actions and linked accounts; margin tiers (the leverage a coin allows at a given notional); HIP-4 outcome markets
PolymarketThe USDC funding of a proxy wallet on Polygon; dispute filings, per-contract volume and Polygon-wide totals; combos and parlays; platform totals (new wallets, deposits against withdrawals, builder volume); trades seen early from the mempool

What the API does hold, product by product: On-chain, Hyperliquid, HL Cohorts, Polymarket and Predict.fun.

License

Data from the API, MCP, the CLI and the Parquet files falls under the Terms of Service: personal use, including trading your own capital; commercial use needs a written agreement. Platform code is FSL-1.1, converting to Apache-2.0, a separate question from the data entirely. The licensing page draws the line precisely.