Welcome to API Academy

Module 11 · Data · ~22 min

Historical data.

By the end of this module, you’ll have real Limitless tape on your laptop, every print, candle, and volume bar your bot might want to learn from before you let it touch a dollar of live money.

To get there, you’ll pull trades, prices, and volume at the granularity your strategy needs. Every backtest in Module 12 and every PnL calculation in Module 13 sits on the historical pipeline you build here.

Data tier · Reference card
Quick answer

How do you get historical data from the Limitless API?

Three endpoints cover it: GET /markets/{addressOrSlug}/oracle-candles returns Chainlink-sourced OHLCV candles (public, no auth), GET /portfolio/trades returns your own AMM fills, and GET /portfolio/history returns your full paginated activity log. Candles take an interval (1m, 5m, 15m, 1h, 4h, or 1d, default 1m) plus from and to in UNIX seconds, and come back as a rows array of timestamped open/high/low/close/volume bars. The portfolio endpoints are authenticated with the HMAC-signed lmts-api-key, lmts-timestamp, and lmts-signature headers. Pull the history once, persist it locally in Parquet, SQLite, or CSV, and feed every backtest and PnL calculation from that same dataset: no re-downloads, no drift between runs.

Endpoints verified 2026-06-10 against the OpenAPI spec.

Authenticated calls carry the three HMAC headers (lmts-api-key + lmts-timestamp + lmts-signature) from Module 03; the legacy X-API-Key is deprecated.

Section 01

What’s available.

Limitless exposes a handful of historical surfaces that together cover every number a backtest needs. Candles are public; your trade history and activity log are authenticated; the orderbook snapshot is public and useful for reconciliation rather than history.

GET
/markets/{addressOrSlug}/oracle-candles public

OHLCV from Chainlink data streams. Query params: interval (1m/5m/15m/1h/4h/1d, default 1m), from, to (UNIX seconds).

GET
/portfolio/trades HMAC

Your own AMM fills. Returns a plain array (no pagination wrapper). Each entry includes blockTimestamp, outcomeTokenPrice, outcomeTokenNetCost, strategy, and the market object.

GET
/portfolio/history HMAC

Full activity log, paginated. Required query: page, limit. Optional: from, to (ISO 8601). Strategy enum is broader than /trades: Buy, Sell, Limit/Market Buy/Sell, Split, Merge, Convert, Claim. Response wraps in { data, totalCount }.

GET
/markets/{slug}/orderbook public

Current orderbook depth. Useful for reconciling a backtest fill model against live quotes, not a historical time series.

Candles

Chainlink-sourced OHLCV. 1m · 5m · 15m · 1h · 4h · 1d intervals.

Your fills

AMM trades via /portfolio/trades; everything else via /portfolio/history.

Auth

Candles are open. Portfolio endpoints require HMAC-signed lmts-* headers.

Section 02

Fetching candles.

Candles are the bread and butter of most backtests. We pull a day of 1-hour bars from /markets/{slug}/oracle-candles and iterate the rows array. The endpoint is public, no API key required, and returns Chainlink-sourced OHLCV. The SDKs don’t currently expose a dedicated candles method, so we call the HTTP endpoint directly in all three languages.

How to run this

  1. No env vars needed, oracle-candles is a public endpoint. Swap btc-above-100k-june for a live market slug from GET /markets/active if the example has since resolved.
  2. Save the snippet above as fetch-candles.ts, then run npx tsx fetch-candles.ts.
  3. You should see ~24 lines of ISO timestamps followed by O H L C V numbers, one per hour of the last day. If the rows array is empty, the slug has no Chainlink feed wired up; pick a different market.
// Module 11, Fetching Candles
// Pull 1-hour OHLCV for the last 24h of a single market.
// Endpoint: GET /markets/{addressOrSlug}/oracle-candles (public, no auth)

const BASE = 'https://api.limitless.exchange';

type CandleRow = {
  timestamp: number;   // unix seconds
  open:   number;
  high:   number;
  low:    number;
  close:  number;
  volume: number;
};

type CandlesResponse = {
  interval:       string;
  source:         string;   // e.g. "chainlink"
  symbol:         string;   // e.g. "BTC/USD"
  timestampStart: number;
  timestampEnd:   number;
  rows:           CandleRow[];
};

async function fetchCandles(slug: string) {
  const now   = Math.floor(Date.now() / 1000);
  const from  = now - 24 * 60 * 60;

  const url = `${BASE}/markets/${slug}/oracle-candles`
            + `?interval=1h&from=${from}&to=${now}`;

  const res  = await fetch(url);
  if (!res.ok) throw new Error(`HTTP ${res.status}`);
  const data = (await res.json()) as CandlesResponse;

  for (const c of data.rows) {
    console.log(
      new Date(c.timestamp * 1000).toISOString(),
      'O', c.open, 'H', c.high, 'L', c.low, 'C', c.close, 'V', c.volume,
    );
  }
}

fetchCandles('btc-above-100k-june').catch(console.error);

Section 03

Fetching trades.

Your own AMM fills live at GET /portfolio/trades. The response is a plain array, no pagination wrapper, so a single authenticated call returns everything. For full activity including CLOB fills, splits, merges, and claims, use GET /portfolio/history with cursor and limit. Both require the HMAC-signed lmts-* headers.

// Module 11, Fetching Your Trade History
// GET /portfolio/trades returns an array of your AMM fills (no pagination).
// Every entry includes blockTimestamp, outcomeTokenPrice, outcomeTokenNetCost,
// strategy ('Buy' | 'Sell'), and the full market object.

import { createHmac } from 'node:crypto';

const BASE = 'https://api.limitless.exchange';

function signedHeaders(method: string, pathWithQuery: string, body = ''): Record<string, string> {
  const ts  = new Date().toISOString();
  const msg = `${ts}\n${method}\n${pathWithQuery}\n${body}`;
  const sig = createHmac('sha256', Buffer.from(process.env.LMTS_TOKEN_SECRET!, 'base64'))
    .update(msg)
    .digest('base64');
  return {
    'lmts-api-key':   process.env.LMTS_TOKEN_ID!,
    'lmts-timestamp': ts,
    'lmts-signature': sig,
  };
}

type Trade = {
  blockTimestamp:      string;   // ISO 8601
  collateralAmount:    string;
  market: {
    conditionId:     string;
    expirationDate:  string;
    id:              string;
    slug:            string;
    title:           string;
  };
  outcomeIndex:        number;   // 0 = YES, 1 = NO
  outcomeTokenAmount:  string;
  outcomeTokenAmounts: string[];
  outcomeTokenNetCost: string;
  outcomeTokenPrice:   string;
  strategy:            'Buy' | 'Sell';
  transactionHash:     string;
};

async function fetchMyTrades(): Promise<Trade[]> {
  const res = await fetch(`${BASE}/portfolio/trades`, {
    headers: signedHeaders('GET', '/portfolio/trades'),
  });
  if (!res.ok) throw new Error(`HTTP ${res.status}`);
  return (await res.json()) as Trade[];
}

(async () => {
  const trades = await fetchMyTrades();
  console.log(`Fetched ${trades.length} AMM trades`);

  for (const t of trades) {
    const side = t.outcomeIndex === 0 ? 'YES' : 'NO';
    console.log(t.blockTimestamp, t.strategy, side,
                '@', t.outcomeTokenPrice, ', ', t.market.slug);
  }

  // For CLOB fills, splits, merges, claims, use /portfolio/history
  // with page + limit (and optional from/to ISO timestamps).
})();

How to run this

  1. Set LMTS_TOKEN_ID + LMTS_TOKEN_SECRET in your environment, or drop them into a .env file next to your script. /portfolio/trades is authenticated and scoped to the API key’s owning wallet.
  2. Save the snippet above as fetch-trades.ts, then run npx tsx fetch-trades.ts.
  3. First line prints Fetched N AMM trades. Following lines show each fill’s ISO timestamp, Buy/Sell, YES/NO side, outcomeTokenPrice, and market slug. Zero trades on a new account is expected, place one in Module 05 first if the array comes back empty.

Section 04

Storing locally.

Don’t re-download the same history every run. Persist it once, then iterate on your strategy locally. Three formats cover 99% of the use cases, pick one and move on.

Parquet

Columnar, compressed, and fast. Pandas, Polars, DuckDB, and Arrow all read it natively. The right default for anything above a few megabytes.

  • ~10× smaller than CSV
  • Typed columns, no parsing
  • Not append-friendly

SQLite

One file, zero infra, indexes for free. Perfect for incremental updates where you keep appending new trades as they arrive.

  • Append + query in one file
  • SQL joins across tables
  • Slower scans than Parquet

CSV

Human-readable, universal, trivially diffable. Best for throwaway exploration or when you want to eyeball the raw numbers in a spreadsheet.

  • Zero tooling needed
  • Fast to append
  • Untyped · bloated · slow
Common questions

Limitless historical data: what people ask

Each answer also ships invisibly as schema.org FAQ data for search engines and AI assistants. Tap a question to expand.

  1. What candle data does Limitless provide?
    Chainlink-sourced OHLCV candles via GET /markets/{addressOrSlug}/oracle-candles, a public endpoint that needs no API key. Query params: interval (1m, 5m, 15m, 1h, 4h, 1d, default 1m) plus from and to as UNIX seconds. The response carries interval, source, symbol, and a rows array of bars. The SDKs don’t currently expose a dedicated candles method, so call the HTTP endpoint directly. An empty rows array means that market has no Chainlink feed wired up.
  2. What is the difference between /portfolio/trades and /portfolio/history?
    GET /portfolio/trades returns your AMM fills as a plain array, no pagination wrapper, with blockTimestamp, outcomeTokenPrice, outcomeTokenNetCost, strategy, and the market object. GET /portfolio/history is the full activity log: cursor-paginated with cursor and limit, a broader strategy enum (Buy, Sell, Limit/Market Buy/Sell, Split, Merge, Convert, Claim), and a response wrapped in { data, totalCount }. Use history for CLOB fills, splits, merges, and claims.
  3. Do Limitless historical endpoints require authentication?
    Candles and the current orderbook snapshot are public. The portfolio endpoints (/portfolio/trades, /portfolio/history) require the three HMAC-signed headers: lmts-api-key, lmts-timestamp, and lmts-signature. The legacy X-API-Key header is deprecated. The trades endpoint is scoped to the API key’s owning wallet, so you only ever see your own fills.
  4. What timezone are Limitless timestamps in?
    UTC. Historical endpoints return Unix timestamps in UTC; treat every timestamp as UTC at the boundary and convert to local time only for display, never for logic. If your code reads them as local time, fills shift by 5–8 hours and a backtest can “fill” before the price was actually quoted, using future data and looking profitable when it isn’t. In Python use datetime.fromtimestamp(ts, tz=UTC); in JS use new Date(ts * 1000) and emit .toISOString().
  5. What is the best format to store market history locally?
    Parquet for anything above a few megabytes: columnar, typed, roughly 10× smaller than CSV, and read natively by Pandas, Polars, DuckDB, and Arrow, though it isn’t append-friendly. SQLite for incremental updates: one file, zero infra, append and query in the same place. CSV for throwaway exploration you want to eyeball in a spreadsheet. Pick one, save the dataset once, and reload it without calling the API a second time.

Section 05

Module checklist.

Tick each item once you’ve actually done it. The Continue button unlocks at 5/5.

Module 11 complete

History in hand.

You no longer have to trust a backtest you can’t audit. When your strategy claims an edge, you can replay the actual exchange data underneath it and see for yourself whether the edge survives a real cost model, before you let any money near it.

Concretely, you have the raw fuel for every backtest, PnL report, and risk check that follows. Here’s what you walk away with:

01

A reusable fetch-candles script that pulls Chainlink-sourced OHLCV from /markets/{slug}/oracle-candles at any of the six supported intervals.

02

A working fetch-trades call against /portfolio/trades (plus the pagination recipe for /portfolio/history) that returns your own fills with blockTimestamp, outcomeTokenNetCost, and strategy.

03

A clear decision tree on where to persist that history, Parquet for analytics, SQLite for ad-hoc queries, CSV for spreadsheet handoffs, so you stop re-downloading the same bars on every run.

Next up: an event-driven backtester that replays those candles through a strategy with a real cost model attached.

Complete the checklist above to unlock