Skip to content
SteamLabs API

Endpoints

Activity & stats

Read your timeline of everything that happened, and the dashboard totals behind it, from code.

Two read-only surfaces: the activity timeline (what happened, event by event) and the statistics behind the dashboard (how big your farm is and how it is trending).

Scopes: activity.read for the timeline, stats.read for the numbers. They are separate on purpose. A reporting integration can be handed the totals without also being handed a blow-by-blow account of every ban, sale and failed task.

Nothing here writes. In particular, reading your timeline does not mark it seen: the unread badge in the dashboard only clears when a person opens the page, so a script polling every minute never hides new activity from you.

The timeline

Every event is durable and immutable, kept for 90 days. The list is newest first.

GET/api/v1/activity

Your activity timeline, newest first.

API key required
Filter Values
category account, wallet, task, workflow, trade, market, profile, billing
type Array of event types, e.g. task.failed, market.listing_sold
severity Array of info, success, warning, danger, gray
problems_only true keeps only danger and warning, the dashboard's toggle
since, before Timestamps. since is inclusive, before exclusive
account_ids Up to 1,000 of your Steam account IDs
accounts A filter object, using the same keys GET /accounts accepts
Bash
curl "https://dashboard.steamlabs.dev/api/v1/activity?problems_only=1&per_page=20" \
  -H "Authorization: Bearer $STEAMLABS_API_KEY"
JSON
{
    "data": [
        {
            "id": "019fb42e-9a61-70d2-818a-f6a56593f3a5",
            "type": "task.failed",
            "title": "Task failed",
            "category": "task",
            "severity": "danger",
            "summary": "Buy from Points Shop · Steam did not answer in time; the request timed out",
            "details": [
                { "label": "Task", "value": "Buy from Points Shop" },
                { "label": "Reason", "value": "Steam did not answer in time; the request timed out" },
                { "label": "Technical details", "value": "Points Shop request timed out" },
                { "label": "Started by", "value": "Automation \"Nightly points run\" (scheduled)" }
            ],
            "failure": {
                "reason": "Steam did not answer in time; the request timed out",
                "raw": "Points Shop request timed out"
            },
            "origin": {
                "kind": "workflow",
                "label": "Automation \"Nightly points run\" (scheduled)",
                "workflow_id": "019fb3f1-6d02-7c3e-9a1b-2e0c5d7f8a90",
                "workflow_name": "Nightly points run",
                "workflow_run_id": "019fb430-11aa-7d5e-8c4f-3b1d9e2a6c71",
                "triggered_by": "schedule"
            },
            "happened_at": "2026-07-30T09:14:22+00:00",
            "day": "2026-07-30",
            "steam_account": { "id": "019fb42e-9a7e-728d-b960-8b4c2162898c", "username": "farm_014" },
            "account_username": "farm_014",
            "subject": {
                "type": "App\\Models\\Task",
                "kind": "task",
                "id": "019fb431-2c88-71ea-b0a3-8ce2f5d19b07",
                "url": "https://dashboard.steamlabs.dev/tasks/019fb431-2c88-71ea-b0a3-8ce2f5d19b07"
            },
            "data": {
                "task_type": "buy_point_shop_items",
                "error": "Points Shop request timed out",
                "account_username": "farm_014",
                "origin": "workflow",
                "workflow_id": "019fb3f1-6d02-7c3e-9a1b-2e0c5d7f8a90",
                "workflow_name": "Nightly points run",
                "workflow_run_id": "019fb430-11aa-7d5e-8c4f-3b1d9e2a6c71",
                "triggered_by": "schedule"
            }
        }
    ],
    "meta": { "page": 1, "per_page": 20, "total": 1, "last_page": 1 }
}

day is the calendar date of happened_at in your timezone, which is the value the dashboard groups its day cards on. Group on it and you get the same timeline the page draws, without having to guess at a timezone.

account_username is the username captured when the event was written, so it survives the account being deleted. steam_account is null once that happens, and for events that were never about one account.

What happened, why, and where it came from

summary is the one-line detail the dashboard prints under the title: which task failed and why, how many bans a check found, how much a top-up moved, which plan a subscription row is about. It is null when the title and the account already say everything the row knows (an account being added, a first login).

details is the same information as label/value pairs, in the language of your account, ready to render as an expanded row. The raw payload is still in data if you would rather format it yourself.

failure is set on failure rows that carry a reason (today: task.failed). reason is the plain-language version; raw is the text the worker reported, included only when reason is a rewrite of it (a timeout, a dropped connection, a missing web session), and null when the reason already is the raw text. Both are null on anything that is not a failure.

origin says where a task came from when SteamLabs knows: kind is workflow for a task spawned by an automation run (with the automation's ID and name, the run ID, and what triggered the run: schedule, manual or event), or automatic for a task SteamLabs queued on its own schedule, such as automatic repricing. It is null for a task started by hand or over the API, which the task row does not tell apart.

subject is the record the event is about. type is the stored model class, kind a short name you can switch on (task, wallet_payment, billing_deposit, billing_ledger_entry, invoice, subscription, steam_account), and url the dashboard page for it. url is null once the record has been pruned (finished tasks are kept 14 days, the timeline 90), so you never link a person to a page that is gone. subject itself is null when the event has no subject.

Paging a long timeline

For a few pages, page is fine. To walk a long history, page with before instead: pass the oldest happened_at you have seen and read the next batch. That stays fast at any depth, while a high page number makes the database count and throw away everything above it.

Bash
curl "https://dashboard.steamlabs.dev/api/v1/activity?before=2026-07-30T09%3A14%3A22%2B00%3A00" \
  -H "Authorization: Bearer $STEAMLABS_API_KEY"

Narrowing to accounts

Naming IDs is capped at 1,000; over that you get 422 bulk_limit_exceeded with the ceiling in max. To follow more accounts than that, describe them instead:

Bash
curl "https://dashboard.steamlabs.dev/api/v1/activity?accounts[login_state][]=banned&problems_only=1" \
  -H "Authorization: Bearer $STEAMLABS_API_KEY"

The accounts object takes the same filters the accounts list does and is resolved server-side, so "everything that happened to every banned account" is one call regardless of how many accounts that is.

One event

GET/api/v1/activity/{id}

One activity event.

API key required

Same shape as a list row. The timeline is denormalized, so a row already carries everything the event knows.

The summary strip

GET/api/v1/activity/summary

Counts for today and the trailing week, with problems and accounts touched.

API key required
JSON
{
    "window_days": 7,
    "timezone": "Europe/Amsterdam",
    "today": 34,
    "week": 214,
    "problems": 6,
    "accounts": 41
}

today starts at midnight in your timezone. The other three cover the trailing seven days.

Statistics

GET/api/v1/stats

Your dashboard totals: accounts, items, inventory value, wallet money, boosted time.

API key required
JSON
{
    "accounts": 412,
    "banned_accounts": 3,
    "item_count": 8241,
    "inventory_value_cents": 1284530,
    "inventory_value_currency": "USD",
    "wallet_cents": { "EUR": 41250, "USD": 9900 },
    "boost_minutes": 184320
}

Wallet money is never summed across currencies. Balances live in each account's own currency, and adding 15 PLN to 15 EUR gives a number that is wrong in both, so you get the whole map and convert with rates you trust. Inventory value is the Steam Market price snapshot, which is USD.

banned_accounts counts VAC, community and game bans after your banned-count exclusions (the same set as GET /api/v1/settings/banned-stat-exclusions). A trade-banned account is not counted. The figure matches the dashboard Accounts card and the Banned card on the accounts page.

boost_minutes is wall-clock boosted time across your accounts, the same figure the Live page reports as hours all time. It is not the per-game ledger, which counts one hour of a ten-game plan ten times.

Inventory value over time

GET/api/v1/stats/inventory-value

Daily inventory value, from the nightly snapshots plus a live point for today.

API key required

days defaults to 30 and accepts up to 365.

JSON
{
    "days": 30,
    "currency": "USD",
    "series": [
        { "day": "2026-07-29", "inventory_value_cents": 1279900 },
        { "day": "2026-07-30", "inventory_value_cents": 1284530 }
    ]
}

History starts the day your account was first snapshotted; days before that are absent rather than zero, because you did not hold an inventory worth nothing, you held no inventory. Today is always present and always read live, so it agrees with GET /stats even before the night's snapshot run.

Tasks completed per day

GET/api/v1/stats/tasks

Completed tasks per day over a trailing window.

API key required

days defaults to 14 and accepts up to 90. The series is zero-filled, so a quiet day is a zero rather than a gap.

JSON
{
    "days": 14,
    "retention_days": 14,
    "series": [
        { "day": "2026-07-29", "completed": 512 },
        { "day": "2026-07-30", "completed": 344 }
    ]
}

retention_days is how long finished tasks are kept. Ask for a window longer than that and the extra days will read as zeros because the rows are gone, which is why days is refused outright above 90 rather than quietly shortened.

Polling these endpoints

All five are cheap by construction: the totals and both series sum columns that are kept up to date as you go, never the item table, and the answers are cached for a minute per account. Polling once a minute costs about as much as polling once an hour.

The one thing worth knowing is that the numbers can be up to a minute behind a change you just made, which is the same freshness the dashboard shows. If you need the exact moment something happened, read the timeline, not the totals.