vike

REST conventions

The rules that hold across every /v1 namespace on data.vike.io — one credential, one hourly counter, one error envelope, one request helper — and where the per-endpoint reference lives.

The REST base URL is:

https://data.vike.io

That is what the live OpenAPI document's own servers array declares, so a generated client picks it up without configuration.

This page is not the endpoint list. The full endpoint reference is API endpoints — the 71 operations of the spec plus the archive's three paths, the schema's three and the WebSocket, one page per endpoint, grouped by namespace. ⚠ One of them is not on the base URL above: the WebSocket wss://vike.io/v1/stream answers on vike.io rather than data.vike.io, so following the base URL cannot reach it. It still validates the same credential and spends the same hourly counter.

What stays here is the set of conventions that hold across the /v1 namespaces, so the namespace pages do not restate them: how you authenticate, how identity and time windows are spelled, what a failure looks like, how a call is labelled, and why a payload is byte-stable.

One request path for all four namespaces

onchain, hyperliquid, hl-cohorts and polymarket share one request path: the same auth, the same rate limiting, the same metering, the same 400 and 503 mapping and the same snapping of aggregated floats apply to every route in them.

/v1/polymarket/* keeps two differences of its own. Its 400 and 503 bodies are {"error": "<message>"}, a plain string, where the other namespaces answer {"detail": ...}; a 401 or 429 there is the same as everywhere else. And its query parameters are read as text and checked by the route: an unknown parameter is a 400 with that body, never the framework's 422. A value the route checks (an interval, a slug_prefix, a trader-route window) is a 400 too, but a malformed number or boolean on the book, tape and settlement routes is ignored and the default applies. Its window parameters are spelled per route too — see Time.

Authenticate

Two credentials are accepted, and either one alone is enough:

curl -H "X-API-Key: $VIKE_API_KEY" \
  "https://data.vike.io/v1/onchain/tokens/search?q=USDC&limit=3"
curl -H "Authorization: Bearer $VIKE_OAUTH_TOKEN" \
  "https://data.vike.io/v1/onchain/tokens/search?q=USDC&limit=3"
  • API key — X-API-Key: vk_.... Create and revoke keys at vike.io/account. HTTP header names are case-insensitive, so the X-API-KEY spelling in the OpenAPI spec is the same header.
  • OAuth 2.1 bearer — Authorization: Bearer vk_oat_..., discovery at https://vike.io/.well-known/oauth-authorization-server.

The bearer is tried first, and a request falls through to the API-key header when the bearer is invalid — including when that rejection came from cache — so a request may carry either or both, and both reach the same account. The spec models this as a list of single-key requirements, which in OpenAPI means OR, not AND.

Every call is counted against one hourly budget shared by MCP and REST; archive downloads are authenticated with the same credential but do not spend it — see Rate limits. Nothing under /v1/ is reachable without a credential, the spec and the interactive docs included: there is no exemption for a schema surface. The spec has a keyless copy off this host: The OpenAPI spec.

⚠ That gate is header-only. Exactly two headers can carry a credential — Authorization and X-API-KEY — and nothing else is consulted. No cookie, no session and no ?token= query parameter authenticates a request to this host, so being signed in to vike.io in the same browser does nothing here.

Key lifecycle, the OAuth discovery document and the revocation window: Authentication.

Conventions

Identity

⚠ There is no API-wide identifier. Each namespace addresses its subject in its own vocabulary, and the spelling is not interchangeable: hyperliquid and hl-cohorts take a symbol, onchain takes an address, and polymarket takes a token_id (one outcome) on its book and tape routes, a condition_id (the market) on /markets/{condition_id} and /settlements/{condition_id}, and a wallet address on /wallets/{address}. The Hyperliquid wallet routes take an address too.

A response echoes the identity it resolved, so a client can pin what it was given rather than re-deriving it.

Time

⚠ There is no API-wide window rule. The parameter list on each endpoint page states its own, and it is the authority. By namespace:

  • hyperliquid and hl-cohorts take start and end as ISO-8601 UTC where a route takes an explicit range, and most of those keep a row stamped exactly at end. /ohlcv, /ohlcv/export and /symbols/hourly are half-open instead — end exclusive, so consecutive windows tile without double-counting a boundary bar — and also accept epoch time. Other routes take a named window (the trader, leaderboard and builder routes) or a number of hours (several cohort series) instead of a range.
  • onchain takes no timestamps: a named window (window, period) or a number of days back from now.
  • 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.

Every timestamp in a response is UTC, but its spelling is per route too: ISO-8601 strings on most hyperliquid, hl-cohorts and onchain routes; epoch milliseconds on Polymarket's book, tape and settlement routes and on the Hyperliquid candles; Polymarket's trader routes return YYYY-MM-DD HH:MM:SS UTC strings and its archive routes dates. Read it off the endpoint page's example.

Errors

Branch on the status code, not the body. The mapping is deliberate: 400 for input the query engine rejected, 401 for a bad or missing credential, 429 for the hourly limit (with Retry-After), 503 for an upstream failure. A store outage is never reported as a 200 with an empty body — a silent empty list is indistinguishable from "no data", which is how bad backfills get built on top of an outage.

Full envelope shapes, and the 422 that comes from parameter validation before the query runs at all: Errors.

Metering

Every call is recorded under a per-surface label, so archive downloads, REST calls and MCP calls meter separately instead of all landing under one name. Two things produce those labels, and they do not share one vocabulary:

  • One is derived from the request path. /v1/<ns>/<resource> becomes api_<ns>_<resource>, /archive/... becomes archive_download, and /mcp becomes mcp. Only the first two path segments after /v1/ are read, and an over-long segment is rejected rather than labelled — so a wallet address or a token id can never reach a usage row.
  • Some routes report a shorter name of their own — api_onchain_wallets, api_hl_positions. /v1/hyperliquid/positions is api_hl_positions under that vocabulary and api_hyperliquid_positions under the path-derived one, so do not assume one name per endpoint when reading usage rows back.

A rate-limited request is logged too, with zero credits — a client hammering past its ceiling would otherwise appear, in usage analytics, to have simply stopped calling. What the budget is, and which surface stopped spending it on 2026-09-02, are on Rate limits.

Determinism and caching

Aggregated floats in a successful payload are snapped to a fixed precision on the way out — once, after the query has run, rather than inside each individual query. The query engine reduces a sum() in an unpinned parallel order, so two executions of one byte-identical query return values differing in their last digits; snapping is what lets a client cache, diff or content-address a response at all.

Every gated response carries Cache-Control: private, no-store. The CDN in front of data.vike.io must not cache an authenticated response — that would bypass both the gate and the metering.

The OpenAPI spec

SurfaceURL
Spec, keyless copyhttps://vike.io/data/openapi.json
Spechttps://data.vike.io/v1/openapi.json
Swagger UIhttps://data.vike.io/v1/docs
ReDochttps://data.vike.io/v1/redoc

The keyless copy needs no credential and is not metered: it is the same document, exported with the code, plus a top-level x-vike-limits block. Every /v1 response names it in a Link header with rel="service-desc", so a client holding only a response can find the spec.

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

The other three sit under /v1/ and behind the same key gate as the operations they describe, so a browser cannot open Swagger UI or ReDoc without injecting a header.

The spec will not carry a code generator

It is generated from the /v1 namespaces it describes; the WebSocket appears in no spec at all. What it does describe, it describes thinly — it declares no response schemas at all: components.schemas holds two entries and both are error types, so every 200 in the document is an undeclared {}. Schema and spec has the full accounting.

  • Hyperliquid OHLCV — price candles now have one home, Hyperliquid's own, read from HL's fill stream and served on the base URL above. Polymarket's /v1/polymarket/ohlcv is a different thing under the same word: one outcome token, priced in probability rather than currency.
  • Polymarket — the reference for that namespace: 13 /v1/polymarket/* routes, all under the base URL above.
  • Bulk history is not a REST concern. Research-scale Polymarket data is the Parquet archive, and Hyperliquid's is daily Parquet files by temporary link: downloads do not spend the hourly budget, and you should fetch whole files.
  • One MCP server, two hosts. https://vike.io/mcp is the canonical URL — see Data tools. https://data.vike.io/mcp reaches the same server with the same tools and the same API key; OAuth clients such as Claude Desktop should use vike.io/mcp.

On this page