Pagination
How limit and offset work on GET /v1/external/tournaments, what the meta envelope contains, and why the other list endpoints return everything at once.
Exactly one endpoint paginates: GET /v1/external/tournaments. It takes limit
and offset, returns its rows under data, and reports the window under meta.
The other two list endpoints — divisions and teams — return a bare JSON array with
every row for that tournament, because both are bounded by how many categories
and entries a single tournament can hold.
The envelope
{
"data": [
{
"idx": "3c9b7f52-6d41-4a8e-b1f0-2e7d5c9a4b18",
"name": "Torneo Apertura 2026"
},
{ "idx": "a41e08d7-33b2-4c96-8f5a-6b0d2e1c7934", "name": "Liga Otoño 2026" }
],
"meta": { "limit": 2, "offset": 0, "total": 37 }
}| Field | Meaning |
|---|---|
data | The rows in this window |
meta.limit | The page size actually applied |
meta.offset | The offset actually applied |
meta.total | Total rows matching the filters, ignoring limit and offset |
Parameters
| Parameter | Type | Default | Range |
|---|---|---|---|
limit | integer | 25 | 1–100 |
offset | integer | 0 | 0 or greater |
Values outside the range are a 400, not a clamp: limit=0, limit=101 and
offset=-1 all fail validation. Non-integers fail too.
Ordering
Tournaments come back ordered by startDate descending — the most recent
season first. The order is stable across pages for a fixed data set.
Walking every page
Stop when you have collected meta.total rows, or when a page comes back short.
async function listAllTournaments(token, params = {}) {
const BASE = 'https://api-production-ea80.up.railway.app/v1';
const limit = 100;
const all = [];
let offset = 0;
let total = Infinity;
while (offset < total) {
const query = new URLSearchParams({
...params,
limit: String(limit),
offset: String(offset),
});
const res = await fetch(`${BASE}/external/tournaments?${query}`, {
headers: { Authorization: `Bearer ${token}` },
});
if (!res.ok) throw new Error(`SETTO API ${res.status}`);
const { data, meta } = await res.json();
all.push(...data);
total = meta.total;
offset += limit;
if (data.length === 0) break; // defensive: never loop forever
}
return all;
}def list_all_tournaments(token, **params):
BASE = "https://api-production-ea80.up.railway.app/v1"
headers = {"Authorization": f"Bearer {token}"}
limit, offset, total, out = 100, 0, None, []
while total is None or offset < total:
res = requests.get(
f"{BASE}/external/tournaments",
headers=headers,
params={**params, "limit": limit, "offset": offset},
timeout=10,
)
res.raise_for_status()
body = res.json()
if not body["data"]:
break
out.extend(body["data"])
total = body["meta"]["total"]
offset += limit
return outFilter before you paginate
status and sport are applied before the window, so meta.total reflects
the filtered set. Narrowing with status=IN_PROGRESS is almost always cheaper
than paging through an archive.
Offset drift
Offset pagination reads a moving list. If a tournament is created, or its
startDate is edited, while you are between pages, a row can shift across the
page boundary and be returned twice or skipped.
For a long export:
- page with
limit=100so there are fewer boundaries to cross; - de-duplicate on
idxas you accumulate; - or pin the window with a filter that will not change under you, such as
status=COMPLETED.
Do not treat offset as a cursor you can persist between runs — re-run the walk
from offset=0 each time.
The other lists
GET /v1/external/tournaments/{idx}/divisions and
GET /v1/external/tournaments/{idx}/teams take no limit or offset and return
a JSON array directly — no data/meta envelope:
[
{
"idx": "7b2f4d16-9c83-4e50-a7d1-3f6c8b204e59",
"name": "Cuarta Fuerza Varonil",
"number": 1
},
{
"idx": "c58a1e93-0b74-42df-96e8-5a3d7f1b28c0",
"name": "Segunda Fuerza Femenil",
"number": 2
}
]Divisions are ordered by number ascending. Teams are ordered by seed
ascending with unseeded entries last, then by name.
If a very large tournament makes those payloads uncomfortable, narrow the teams
call with the division filter — ?division={divisionIdx} — rather than asking
for a page size that does not exist.