Concepts
Pagination and filtering
Work through large collections without pulling your whole account down the wire.
Every collection endpoint is paginated. There is no way to ask for everything at once, on purpose: accounts with hundreds of thousands of inventory items exist, and an endpoint that tried to serialise all of them would fail for the people who need it most.
The envelope
{
"data": [
{ "id": "019fb42e-9a61-70d2-818a-f6a56593f3a5", "username": "farm_001" },
{ "id": "019fb42e-9a7e-728d-b960-8b4c2162898c", "username": "farm_002" }
],
"meta": {
"page": 1,
"per_page": 50,
"total": 418,
"last_page": 9
}
}Single records are returned bare, with no envelope:
{
"id": "019fb42e-9a61-70d2-818a-f6a56593f3a5",
"username": "farm_001"
}Paging
| Parameter | Default | Max | Notes |
|---|---|---|---|
page |
1 |
1-indexed | |
per_page |
50 |
200 |
Values above the max are clamped, not rejected |
Asking for per_page=5000 gives you 200 and says so in meta.per_page. Read the value back rather than assuming you got what you asked for.
curl "https://dashboard.steamlabs.dev/api/v1/accounts?page=2&per_page=200" \
-H "Authorization: Bearer $STEAMLABS_API_KEY"def all_accounts():
page = 1
while True:
body = requests.get(
"https://dashboard.steamlabs.dev/api/v1/accounts",
headers={"Authorization": f"Bearer {api_key}"},
params={"page": page, "per_page": 200},
).json()
yield from body["data"]
if page >= body["meta"]["last_page"]:
return
page += 1async function* allAccounts() {
let page = 1;
while (true) {
const response = await fetch(
`https://dashboard.steamlabs.dev/api/v1/accounts?page=${page}&per_page=200`,
{ headers: { Authorization: `Bearer ${apiKey}` } },
);
const { data, meta } = await response.json();
yield* data;
if (page >= meta.last_page) return;
page += 1;
}
}Sorting
| Parameter | Notes |
|---|---|
sort |
A field name from that endpoint's own list |
direction |
asc or desc. Most listings default to desc |
Each endpoint's page lists the fields it can sort by. Sorting by anything else returns 422, so a typo tells you rather than quietly falling back to the default.
Two listings do it differently and say so on their own page: GET /api/v1/profiles and GET /api/v1/profile-groups take the direction inside sort (?sort=-times_used) and have no direction parameter. GET /api/v1/trade-offers and GET /api/v1/trade-routing-templates spell the direction order instead of direction.
Filtering
Filters are plain query parameters. Every endpoint documents its own, but the shared conventions are:
| Shape | Example | Meaning |
|---|---|---|
| Exact | ?boost=boosting |
Equals |
| Multiple | ?ban_status[]=vac&ban_status[]=game |
Any of. Repeat the parameter with [] |
| Search | ?search=farm_0 |
Substring match on the endpoint's searchable fields |
| Date range | ?created_after=2026-07-01&created_before=2026-07-31 |
Inclusive, ISO 8601 |
| Presence | ?details_never_refreshed=true |
Booleans take true / false (or 1 / 0) |
A filter that takes several values is an array parameter. Sending ?ban_status=vac where the endpoint expects a list is a 422, not a single-value match: the rule is array, and being told is better than being silently ignored. Two exceptions, both documented where they live: group_ids on GET /api/v1/profiles also accepts a comma-separated string, and status on GET /api/v1/boost/plans accepts a bare value as well as a list.
Filters combine with AND:
curl "https://dashboard.steamlabs.dev/api/v1/accounts?session=online&ban_status[]=vac&sort=created_at&direction=desc" \
-H "Authorization: Bearer $STEAMLABS_API_KEY"Filter server-side wherever you can. Pulling 5,000 accounts to keep 40 of them costs 25 requests and a lot of waiting, where the filtered call costs one.
Counting without fetching
meta.total is the count of everything matching your filters, not just the current page. To count something, request per_page=1 and read meta.total:
curl "https://dashboard.steamlabs.dev/api/v1/tasks?status=failed&per_page=1" \
-H "Authorization: Bearer $STEAMLABS_API_KEY"A note on stable paging
Paging is offset-based, so a collection that changes while you walk it can shift under you: a row inserted at the front pushes one row from page 1 onto page 2, and you would see it twice.
For collections that change constantly (tasks, inventory during a sell run), either sort by something stable such as created_at and filter by a fixed upper bound, or de-duplicate by ID as you collect.