Paginate Through Results

Bybit uses exactly two pagination shapes across the public market surface, and both are done for you — each of the five cursor endpoints and each of the six time-window endpoints carries a *_paged async iterator, generated from its spec.

Cursor Pagination

Five endpoints return an opaque nextPageCursor. Pass it back as cursor to get the next page; an empty string means there are no further pages.

Endpoint Request Response Iterator
market.instruments (linear, inverse, option) cursor, limit nextPageCursor instruments_paged
market.open_interest cursor, limit nextPageCursor open_interest_paged
market.risk_limit cursor nextPageCursor risk_limit_paged
market.delivery_price cursor, limit nextPageCursor delivery_price_paged
market.long_short_ratio cursor, limit nextPageCursor long_short_ratio_paged

market.instruments with category='spot' is not paginated — that variant has no nextPageCursor field at all, and returns every pair in one response.

Each *_paged method takes the same arguments as the single-request one minus cursor, which it drives itself, plus an optional max_pages. It yields whole pages, so the response type is unchanged:

from typed_bybit import Bybit

async with Bybit.new(public=True) as client:
  symbols: list[str] = []
  async for page in client.http.market.instruments_paged(category='linear', limit=200):
    symbols += [i['symbol'] for i in page['list']]
  print(len(symbols))

Pass max_pages when you only want the first few, which also bounds a walk over a surface that keeps growing:

from typed_bybit import Bybit

async with Bybit.new(public=True) as client:
  pages = [
    page
    async for page in client.http.market.open_interest_paged(
      category='linear', symbol='BTCUSDT', interval_time='1h', limit=200, max_pages=3,
    )
  ]
  print(len(pages), sum(len(page['list']) for page in pages))

The equivalent hand-written loop, if you want to hold the cursor yourself:

from typed_bybit import Bybit

async with Bybit.new(public=True) as client:
  symbols: list[str] = []
  cursor = None
  while True:
    page = await client.http.market.instruments(category='linear', limit=200, cursor=cursor)
    assert page['category'] != 'spot'
    symbols += [i['symbol'] for i in page['list']]
    cursor = page['nextPageCursor']
    if not cursor:
      break
  print(len(symbols))

Cursor values are URL-encoded key/value pairs such as first%3D0GUSDT%26last%3D10000SATSUSDT or lastid%3D9826950%26lasttime%3D1785326400. The format differs per endpoint and is not stable — treat cursors as opaque. Do not parse them, and do not construct one yourself.

Time-Window Pagination

Six endpoints have no cursor. They take a time window and return the rows inside it, so their iterators walk by moving the window you passed, keeping its width and stepping one millisecond past the edge just covered.

Endpoint Request Iterator Note
market.kline start, end, limit kline_paged limit 1–1000, default 200
market.mark_price_kline start, end, limit mark_price_kline_paged limit 1–1000, default 200
market.index_price_kline start, end, limit index_price_kline_paged limit 1–1000, default 200
market.premium_index_price_kline start, end, limit premium_index_price_kline_paged limit 1–1000, default 200
market.funding_history start_time, end_time, limit funding_history_paged limit 1–200, default 200
market.historical_volatility start_time, end_time historical_volatility_paged window ≤ 30 days, both or neither

Pass a first window and the walk repeats it backwards through history until a window comes back empty. Both arguments are required here — the width you choose is the step the walk takes, so there is no walk without one:

from datetime import datetime, timedelta, timezone
from typed_bybit import Bybit

async with Bybit.new(public=True) as client:
  end = datetime.now(timezone.utc)
  rates = []
  async for page in client.http.market.funding_history_paged(
    category='linear', symbol='BTCUSDT',
    start_time=end - timedelta(hours=24), end_time=end, limit=200, max_pages=30,
  ):
    rates += page['list']
  print(len(rates), rates[0]['fundingRateTimestamp'], rates[-1]['fundingRateTimestamp'])

Choose a window the endpoint can answer in one response — at most limit rows. A wider one is capped by the venue, and the walk moves on to the next window rather than finishing the one it truncated. For klines that is limit × the interval; the example below walks four minutes of one-minute candles at a time.

from datetime import datetime, timedelta, timezone
from typed_bybit import Bybit

async with Bybit.new(public=True) as client:
  end = datetime.now(timezone.utc)
  async for page in client.http.market.kline_paged(
    category='linear', symbol='BTCUSDT', interval='1',
    start=end - timedelta(minutes=4), end=end, limit=200, max_pages=5,
  ):
    print(len(page['list']), page['list'][0][0], page['list'][-1][0])

market.historical_volatility walks forwards instead, because it returns its samples oldest first. It is also stricter — the window must be 30 days or less, and start_time and end_time must be supplied together or not at all.

Both bounds of every one of these endpoints are inclusive, which is why the iterator steps a millisecond past each edge. A hand-written loop that reused the previous bound verbatim would return the boundary row again on every iteration and never finish:

from datetime import datetime, timedelta, timezone
from typed_bybit import Bybit

async with Bybit.new(public=True) as client:
  end = datetime.now(timezone.utc)
  start = end - timedelta(days=30)
  rates = []
  while True:
    page = await client.http.market.funding_history(
      category='linear', symbol='BTCUSDT',
      start_time=start, end_time=end, limit=200,
    )
    batch = page['list']
    if not batch:
      break
    rates += batch
    end = batch[-1]['fundingRateTimestamp'] - timedelta(milliseconds=1)
    if len(batch) < 200:
      break
  print(len(rates))

Write that loop when you want to walk an unbounded stretch of history without choosing a window width: it moves the bound onto the last row it received, which is a different walk from the generated one and needs the response to do it.

Beyond Market Data

The same cursor pattern shows up well past market.*: trade.open_orders_paged and trade.order_history_paged, position.list_paged, asset.deposit.record_paged and asset.withdraw.record_paged, and account.transaction_log_paged all walk a nextPageCursor the same way market.instruments_paged does above.

from typed_bybit import Bybit

async with Bybit.new() as client:
  orders = []
  async for page in client.http.trade.order_history_paged(category='spot', limit=50):
    orders += page['list']
  print(len(orders))

Endpoints With No Pagination

market.tickers, market.orderbook, market.rpi_orderbook, market.full_orderbook, market.recent_trades, market.instruments (spot), market.insurance, market.new_delivery_price, market.index_price_components, market.price_limit, market.adl_alert, market.fee_group, and market.time return everything in one response. limit, where it exists, truncates rather than pages.