Historical REST
All time-series data the platform persists is available through five REST endpoints with identical pagination and filtering semantics. Response payloads mirror the WebSocket message shapes — a parser that handles a live print frame can consume historical trade rows unchanged.
Authentication is optional but tier-determines lookback: free tier = last 1 day; paid (api) tier = full retention (~90 days today, expanding to inception as backfill completes).
Endpoints
GET /v1/history/{exchange}/{symbol}/quotes ?from=&to=&limit=&cursor=GET /v1/history/{exchange}/{symbol}/trades ?from=&to=&limit=&cursor=GET /v1/history/{exchange}/{symbol}/funding ?from=&to=&limit=&cursor=GET /v1/history/{exchange}/{symbol}/liquidity ?from=&to=&limit=&cursor=&action=mint|burnGET /v1/history/rates/{address} ?from=&to=&limit=&cursor=| Endpoint | Returns | Message type |
|---|---|---|
.../quotes | 5-second sampled top-of-book rows | QuoteMessage |
.../trades | One row per trade | PrintMessage |
.../funding | Funding rate rows | FundingRateMessage |
.../liquidity | LP mint/burn events | LiquidityMessage |
/rates/{address} | Yield-market snapshots (Pendle + Spectra UNION) | RateMarketMessage |
The .../liquidity endpoint is meaningful only for AMM venues (uni, univ4, univ4chain, sushi, pancake, balancer).
The /rates/{address} endpoint is keyed by contract address (lowercase), not by venue + symbol. The same address uniquely identifies a market across both Pendle and Spectra. Historical queries for yield markets require the address; use GET /v1/instruments or GET /v1/symbols/pendle to resolve PT symbols to addresses.
Path parameters
| Parameter | Type | Notes |
|---|---|---|
exchange | string | Lowercase venue id. See Venues & Symbols. |
symbol | string | Canonical per-venue symbol. URL-encode slashes: WETH/USDC → WETH%2FUSDC. Most HTTP libraries handle this automatically when using a params dict. |
address | string | Lowercase EVM address (0x + 40 hex). |
Query parameters
| Parameter | Type | Default | Notes |
|---|---|---|---|
from | string | now − 24h | ISO 8601 (2026-04-01T00:00:00Z or 2026-04-01) or epoch ms |
to | string | now | Same formats as from |
limit | integer | 1000 | Rows per page. Hard cap: 10,000. |
cursor | string | — | Opaque cursor from the previous page’s next_cursor. |
action | string | — | Liquidity endpoint only. Filter to mint or burn; omit for both. |
Response shape
{ "data": [ /* array of message objects, oldest first */ ], "next_cursor": "MTc0ODI3NTIwMDAwMA=="}data is ordered oldest first. next_cursor is null when no further pages exist. Pass it back as ?cursor=... to get the next page. Do not decode or construct the cursor manually — the format is internal and may change.
Lookback limits
| Tier | Lookback |
|---|---|
none (free / anonymous) | 1 day |
api (paid) | Full DB retention (no hard cap; ~90 days today, expanding to inception) |
Querying outside your tier’s window returns HTTP 403:
{ "error": "lookback_too_far_for_tier", "message": "Free tier limited to 1 day of history. Authenticate with an API key for full historical access.", "tier": "none", "freeLookbackMs": 86400000}Authentication: Authorization: Bearer <JWT>. Tier is resolved from the JWT’s tier claim. Anonymous requests default to none. See Authentication for how to obtain a JWT or API key.
Pagination
import httpx
def fetch_all(exchange, symbol, kind, start, end, base="https://api.mackinac.io", headers=None): cursor = None while True: params = {"from": start, "to": end, "limit": 10_000} if cursor: params["cursor"] = cursor r = httpx.get( f"{base}/v1/history/{exchange}/{symbol}/{kind}", params=params, headers=headers or {} ) r.raise_for_status() body = r.json() yield from body["data"] cursor = body["next_cursor"] if cursor is None: return
# Example: all HL ETH trades for a date rangejwt = "eyJhbGciOi..."for row in fetch_all("hl", "ETH", "trades", "2026-04-01", "2026-04-08", headers={"Authorization": f"Bearer {jwt}"}): print(row["price"], row["size"], row["side"])Examples
Pull recent HL ETH trades
GET /v1/history/hl/ETH/trades?from=2026-04-01&to=2026-04-08&limit=10000{ "data": [ {"type": "print", "exchange": "hl", "symbol": "ETH", "price": 3450.5, "size": 1.2, "side": 1, "time": 1743465601000}, ... ], "next_cursor": "MTc0Mz..."}Pull AMM trades (URL-encoded slash)
GET /v1/history/uni/WETH%2FUSDC/trades?from=2026-04-01Pull LP events, mints only
GET /v1/history/uni/WETH%2FUSDC/liquidity?action=mint&from=2026-04-01Pull yield-market history by address
GET /v1/history/rates/0xc62d75593dad6c451173553593f86d80bf29dfe6Returns rate_market rows from both Pendle and Spectra history tables (UNION). The exchange field on each row identifies which venue produced it.
Pull funding history
GET /v1/history/hl/ETH/funding?from=2026-04-01&to=2026-04-08Error codes
| HTTP | error code | Meaning |
|---|---|---|
| 400 | invalid_time | from or to couldn’t be parsed |
| 400 | invalid_limit | limit outside [1, 10000] |
| 400 | invalid_cursor | Cursor base64 decode failed |
| 403 | lookback_too_far_for_tier | Requested window exceeds the caller’s tier limit |
| 404 | not_found | Unknown exchange/symbol — the backend has never persisted any rows for this key |
| 500 | db_error | TimescaleDB query failed; retry with exponential backoff |
See Errors for the full error reference and retry decision matrix.