Skip to content

X-Trader API cursor pagination

X-Trader Public API list endpoints use opaque cursor pagination: each response returns data.items and data.next_cursor, and the next page is requested by passing next_cursor unchanged as the cursor query parameter. Pages contain 20 items by default and at most 100 (limit). A null next_cursor marks the last page, and a cursor is valid only for the same token and the same filters.

Primary source: X-Trader API reference. This page is an independent explanation of X-Trader Public API v1.0.0; it is not affiliated with X-Trader.

Endpoint Scope Order
GET /sniper/configs sniper:read updated_at, then id
GET /copy-trade/configs copytrade:read updated_at, then id
GET /transfer-snipe/configs transfersnipe:read updated_at, then id
GET /sniper/templates sniper:read not stated in the spec
GET /copy-trade/templates copytrade:read not stated in the spec

GET /labels is not paginated (see below). The spec states that config lists are ordered by updated_at and id for stable paging but does not state the direction (ascending or descending).

Config lists (sniper, copy-trade, transfer-snipe)

Section titled “Config lists (sniper, copy-trade, transfer-snipe)”
Parameter Type Default Constraints Meaning
limit integer 20 1-100 Items per page
cursor string none max 2048 characters Opaque signed cursor from the previous page
status enum none active, paused Filter by execution state (is_active)
q string none max 64 characters Search in remark and target_wallet, own resources only
label_id string none label UUID or literal unlabeled Only tasks with this label, or with no label

A label_id UUID that is missing or owned by another account returns 404 not_found.

Parameter Type Default Constraints Meaning
limit integer 20 1-100 Items per page
cursor string none max 2048 characters Opaque signed cursor, bound to the token and the search query
q string none max 64 characters Search template names only

Template lists have no status or label_id filter (templates never carry a label).

{
"data": {
"items": [ { "id": "...", "version": 3, "is_active": false } ],
"next_cursor": "opaque-string-or-null"
},
"meta": { "request_id": "req_0123456789abcdef" }
}
Field Type Meaning
data.items array Resources of the page, in the same shape as single GET results
data.next_cursor string or null (required) Cursor for the next page; null when there are no more pages
meta.request_id string Request identifier

Items are returned as stored snapshots and may contain legacy fields (see write rules). List responses do not carry per-item ETag headers; use each item’s version or a single GET before writing.

  • Treat the cursor as opaque: pass it unchanged, never parse, decode or edit it.
  • It is bound to the token that produced it. A cursor from one token is not usable with another (including a rotated token, as practical guidance).
  • It is bound to the filters (status, q, label_id on config lists; q on template lists). After changing any filter, restart from the first page without cursor.
  • Keep limit and filters identical across pages of one traversal.
  • The spec does not document cursor expiry or the error code for an invalid cursor. As practical guidance, on a 400 error for a cursor request restart from the first page.

First page of paused sniper configs, 50 per page:

Terminal window
curl 'https://x-trader.ai/api/v1/sniper/configs?limit=50&status=paused' \
--header "Authorization: Bearer $X_TRADER_API_TOKEN"

Next page (the cursor must be URL-encoded):

Terminal window
curl --get 'https://x-trader.ai/api/v1/sniper/configs' \
--header "Authorization: Bearer $X_TRADER_API_TOKEN" \
--data-urlencode 'limit=50' \
--data-urlencode 'status=paused' \
--data-urlencode "cursor=$NEXT_CURSOR"

Python loop fetching every page:

import os
import requests
BASE = "https://x-trader.ai/api/v1"
H = {"Authorization": f"Bearer {os.environ['X_TRADER_API_TOKEN']}"}
def fetch_all(path, **filters):
params = {"limit": 100, **filters}
items = []
while True:
r = requests.get(f"{BASE}{path}", headers=H, params=params, timeout=15)
r.raise_for_status()
data = r.json()["data"]
items.extend(data["items"])
cursor = data["next_cursor"]
if cursor is None:
return items
params = {**params, "cursor": cursor} # same limit and filters, new cursor
active_copy = fetch_all("/copy-trade/configs", status="active")
unlabeled_snipers = fetch_all("/sniper/configs", label_id="unlabeled")
templates = fetch_all("/sniper/templates", q="night")
print(len(active_copy), len(unlabeled_snipers), len(templates))

requests URL-encodes params, so the cursor is passed through unchanged.

GET /labels takes no query parameters and returns every label of the account in data.items, with no next_cursor. There are at most 20 labels, ordered by sort_order, then created_at. See labels.

How many items does an X-Trader list return?

Section titled “How many items does an X-Trader list return?”

20 by default. Set limit to any integer from 1 to 100.

data.next_cursor is null. Otherwise pass it unchanged as the cursor query parameter on the next request.

No. Cursors are bound to the token and the filters of the first request. After changing status, q or label_id, restart from the first page without a cursor.

No. GET /labels takes no parameters and returns all labels (at most 20) in data.items without next_cursor.