Skip to content
SteamLabs API

Endpoints

Steam accounts

List, filter, create and act on your Steam accounts from code, one at a time or a hundred thousand at once.

Everything the Accounts page does, over HTTP: add accounts, filter the fleet the way the table filters it, and run any of its bulk actions against a selection.

Two scopes cover the domain. accounts.read for the listings and the read-only sub-resources, accounts.write for everything that changes an account or queues work for one. Tags share them, because a tag is just a label on an account.

Credentials are write-only

No response ever contains a password, shared secret, identity secret or session token. Not masked, not truncated, not the last four characters.

You already have the credential (you sent it), so returning it buys you nothing, while a leaked read-only key would otherwise hand over a working set of Steam logins. What you get instead is whether one exists:

JSON
{
    "steam_guard": { "enabled": true, "two_factor": true, "has_shared_secret": true, "has_identity_secret": false },
    "session": { "online": true, "has_stored_session": true, "login_state": null }
}

Send a new value to change a secret, or null to clear it. Omitting a field leaves it alone.

List accounts

GET/api/v1/accounts

Your Steam accounts, paginated and filterable.

API key required
Bash
curl "https://dashboard.steamlabs.dev/api/v1/accounts?ban_status[]=vac&per_page=1" \
  -H "Authorization: Bearer $STEAMLABS_API_KEY"
JSON
{
    "data": [
        {
            "id": "019fb42e-9a61-70d2-818a-f6a56593f3a5",
            "username": "farm_017",
            "persona_name": "Ada",
            "steam64_id": "76561198000000017",
            "steam_partner_id": "39734289",
            "avatar_url": "https://avatars.steamstatic.com/…_full.jpg",
            "summary": null,
            "country": "NL",
            "store_country": "NL",
            "email": "[email protected]",
            "email_verified": true,
            "profile_configured": true,
            "profile_needs_setup": false,
            "trade_url": "https://steamcommunity.com/tradeoffer/new/?partner=39734289&token=AbCd1234",
            "steam_guard": { "enabled": true, "two_factor": true, "has_shared_secret": true, "has_identity_secret": true },
            "session": {
                "online": true,
                "has_stored_session": true,
                "stored_session_expires_at": "2026-08-27T09:15:00+00:00",
                "login_state": null,
                "login_state_at": null
            },
            "wallet": {
                "balance_cents": 1234,
                "pending_balance_cents": 594,
                "pending_available_at": null,
                "pending_available_until": "2026-07-31T08:00:00+00:00",
                "currency": "EUR"
            },
            "point_shop": {
                "balance": 2990,
                "points_earned": 3690,
                "points_spent": 700,
                "balance_refreshed_at": "2026-08-05T14:45:00+00:00"
            },
            "spend": {
                "external_funds_used_cents": 2734,
                "package_only_cents": 1601,
                "currency": "USD",
                "refreshed_at": "2026-08-18T12:00:00+00:00"
            },
            "profile_items": {
                "equipped": {
                    "profile_background": {
                        "community_item_id": "39213089438",
                        "app_id": 2243720,
                        "name": "Outstanding Story-Rich Game",
                        "title": "Outstanding Story-Rich Game",
                        "image_url": "https://shared.fastly.steamstatic.com/community_assets/images/items/2243720/51fdb36bd2a933344c9236cb47a0abddf17e32da.jpg"
                    },
                    "mini_profile_background": null,
                    "avatar_frame": {
                        "community_item_id": "39213089373",
                        "app_id": 730,
                        "name": "Akihabara Accept",
                        "title": "Akihabara Accept",
                        "image_url": "https://shared.fastly.steamstatic.com/community_assets/images/items/730/c2a822b6422d7a2c9e72ec74bf54139db8723ef0.png"
                    },
                    "animated_avatar": null
                },
                "refreshed_at": "2026-08-15T11:02:00+00:00"
            },
            "market": {
                "eligibility": "eligible",
                "allowed": true,
                "unlocks_at": null,
                "restriction_reason": null,
                "checked_at": "2026-07-30T14:02:11+00:00"
            },
            "bans": {
                "banned": false,
                "vac": false,
                "community": false,
                "game": false,
                "economy": false,
                "vac_count": 0,
                "game_count": 0,
                "days_since_last_ban": null,
                "checked_at": "2026-07-30T14:02:11+00:00"
            },
            "inventory": { "item_count": 412, "synced_item_count": 412, "value_cents": 18355, "refreshed_at": "2026-07-30T13:00:00+00:00" },
            "cs2": {
                "level": 21,
                "xp": 4820,
                "armory_balance": 300,
                "armory_pass_count": 1,
                "armory_unactivated_pass_count": 0,
                "armory_progress_percentage": 42.5,
                "weekly_drops": null,
                "service_medal_claimable": false,
                "service_medal_claimed": true,
                "refreshed_at": "2026-07-30T12:00:00+00:00"
            },
            "tf2": { "premium": false, "additional_backpack_slots": null, "trade_ban_expires_at": null, "refreshed_at": null },
            "boost": { "minutes_total": 12400, "state": "boosting", "plan_id": "019fb431-…" },
            "proxies": { "pinned_count": 1, "group_count": 0 },
            "group": { "id": "019fb433-…", "name": "Prime" },
            "tags": [ { "id": "019fb432-…", "name": "prime", "color": "#3b82f6" } ],
            "billing_address": { "first_name": "Ada", "last_name": "Lovelace", "address": "…", "city": "Amsterdam", "country": "NL", "postal_code": "1011AB" },
            "billing_address_is_custom": false,
            "privacy_settings": { "profile": 3, "inventory": 3 },
            "details_refreshed_at": "2026-07-30T14:02:11+00:00",
            "ownership_refreshed_at": "2026-07-29T08:00:00+00:00",
            "created_at": "2026-07-01T09:15:00+00:00",
            "updated_at": "2026-07-30T14:02:11+00:00"
        }
    ],
    "meta": { "page": 1, "per_page": 1, "total": 1, "last_page": 1 }
}

session.online is null rather than false when no worker is reporting. That means "we cannot see", not "offline", and it is worth branching on: absence from the session registry only means offline while the registry is live.

profile_items.equipped lists the cosmetics on the Steam profile, with one key per slot and null for an empty slot. All four keys are always present, so a missing item is null rather than an absent key. refreshed_at is null until a refresh_details task has read them, which is also what keeps them current. Emoticons, stickers and chat effects never appear here: they are consumed per use rather than equipped.

Filters

These are the accounts table's own filters, by the same names.

Parameter Values
search Username, persona name, or Steam64 id
ban_status community, vac, game, trade. Repeat it (ban_status[]=) to match any of several
not_banned true for accounts without a VAC, community, or game ban. Trade-only and never-checked accounts match
login_state disabled, locked, suspended, banned, deleted, not_found. Repeatable
marketplace_access steam, csfloat, marketcsgo, skinland, dmarket, skinport, assetpay: accounts that can sell there right now. Repeatable
market_unlocking_soon true for restrictions that lift within a week
steam_market_ineligible true for accounts that cannot sell on the Community Market
profile_not_set_up true for accounts confirmed never to have saved a Community profile
details_never_refreshed true for accounts no refresh has ever run against
boost boosting, on_plan, not_on_plan
session online, offline
tags Tag ids. Repeatable, matching any of them
exclude_tags Tag ids. Repeatable, dropping an account that carries any of them
groups Account group ids. Repeatable, matching any selected group
include_ungrouped true to include accounts with no group. ORed with groups
wallet_min Minimum available Steam wallet value in USD across mixed currencies
wallet_max Maximum available Steam wallet value in USD across mixed currencies
has_pending_balance true for accounts with a pending Steam wallet balance greater than zero
wallet_pending_min Minimum pending Steam wallet value in USD across mixed currencies
wallet_pending_max Maximum pending Steam wallet value in USD across mixed currencies
sort created_at (default), username, details_refreshed_at, inventory_refreshed_at, wallet_balance, wallet_pending_balance, point_shop_balance, external_funds_used, cs_level, item_count, items_count, login_state
direction asc, desc

wallet.balance_cents is available to spend. wallet.pending_balance_cents is held by Steam and is not included in the available balance. When Steam exposes its estimated release window, wallet.pending_available_at is the earliest expected release and wallet.pending_available_until is when all pending funds are expected to be available. Either timestamp can be null when Steam does not provide that boundary.

spend is Steam's External Funds Used snapshot from a refresh_details task: lifetime real money applied to the account, not the current wallet. package_only_cents is store-package spend only. Every field is null until a refresh has read the AccountSpend page.

details_never_refreshed=true is the one to run after an import. Until a refresh lands there is no Steam64 id, no wallet, no profile state and no privacy settings, so most of the rest of the API has nothing to work with.

Read, create, update, delete

GET/api/v1/accounts/{id}

One account.

API key required
POST/api/v1/accounts

Add one account.

API key required
PATCH/api/v1/accounts/{id}

Edit credentials, trade URL, or billing address.

API key required
DELETE/api/v1/accounts/{id}

Delete one account.

API key required
Bash
curl -X POST "https://dashboard.steamlabs.dev/api/v1/accounts" \
  -H "Authorization: Bearer $STEAMLABS_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"username":"farm_018","password":"…","shared_secret":"…","identity_secret":"…"}'

Usernames are unique per account holder, not globally: two customers can farm the same public alias.

PATCH accepts trade_url as a whole Steam trade URL and stores only its token. A URL carrying a different partner= is refused, because it belongs to another account and would send this one's items to a stranger.

billing_address follows the panel: send the fields to set it (and mark it custom, so refreshes stop overwriting it), or null to clear it back to auto-generated.

Single and bulk account create payloads accept nullable account_group_id. PATCH /accounts/{id} accepts the same field. The group must belong to the API key owner.

DELETE answers 409 account_in_trade_protection while the account is delivering an AssetPay pool sale that is still inside Steam's 7 day trade protection. Only that account can see the trade if it gets reversed, and AssetPay refunds their buyer on our word, so it has to stay until the hold ends. Nothing else blocks a delete, and retrying after the hold works.

Creating goes through your plan's account cap. Over it, you get 403 with plan_limit_reached and the upgrade path:

JSON
{
    "message": "Your plan covers 25 Steam accounts.",
    "code": "plan_limit_reached",
    "plan": { "heading": "Account limit reached", "upgrade_url": "https://dashboard.steamlabs.dev/subscription", "max_steam_accounts": 25, "remaining": 0 }
}

Add many at once

POST/api/v1/accounts/bulk

Add up to 1,000 accounts in one call.

API key required
JSON
{
    "accounts": [
        { "username": "farm_019", "password": "…" },
        { "username": "farm_020", "password": "…", "shared_secret": "…" }
    ]
}

All or nothing. A duplicate anywhere in the payload, or against an account you already have, fails the whole batch before anything is written, so you never have to work out which half landed. The answer is 201 with created and the full records.

Acting on many accounts

Every action below takes a selection, in one of two shapes.

JSON
{ "account_ids": ["019fb42e-…", "019fb42f-…"] }

An explicit list, capped at 1,000 ids. It is applied immediately and the response tells you what happened. Ids you do not own are simply not in the selection; they are never refused individually, since refusing them differently from unknown ids would confirm they exist.

JSON
{ "filters": { "details_never_refreshed": true, "tags": ["019fb432-…"] } }

The same filter object the listing accepts. It is resolved on the server and the ids are never materialized, so "every account with no proxy" stays a filter definition no matter how many accounts match. Filter selections always run asynchronously and answer 202.

Send one or the other, never both, and never neither: a request with no selection would otherwise mean "everything I own" by accident, which is the most expensive mistake this API can make. Over the id cap you get 422:

JSON
{ "message": "A bulk request may carry at most 1000 account ids…", "code": "bulk_limit_exceeded", "max": 1000 }

There is no "everything" filter selection here: {"filters": {}} counts as a request with no selection and is refused alongside sending nothing at all. To act on every account you own, name a filter that matches them all. Run any selection through POST /api/v1/tasks/preview first if you want the count before you commit.

Queue work on the fleet

Endpoint What it queues
POST /api/v1/accounts/refresh-details A full details refresh (Steam64 id, wallet, Points Shop balance, profile, privacy, bans)
POST /api/v1/accounts/refresh-inventory An inventory-only refresh, much lighter than a full details refresh
POST /api/v1/accounts/refresh-wallet A wallet-only refresh (balance, pending funds, currency), much faster than a full details refresh
POST /api/v1/accounts/refresh-point-shop-balance Refreshes the Points Shop balance and lifetime earned and spent totals
POST /api/v1/accounts/refresh-trade-url Re-scrapes and overwrites the stored trade URL token
POST /api/v1/accounts/login A bare login, so a batch starts against warm sessions
POST /api/v1/accounts/sign-out-everywhere Deauthorizes every Steam session, ours included
POST /api/v1/accounts/check-bans A ban check (skips accounts with no Steam64 id yet)
POST /api/v1/accounts/set-profile Sets the Community profile across the selection
POST /api/v1/accounts/change-profile-privacy Flips privacy settings across the selection

All ten require an Idempotency-Key header: they cost fleet capacity and several of them change something on Steam, so a retry after a timeout must replay rather than repeat.

All ten refuse with 503 maintenance_mode during platform maintenance, like every other endpoint that queues new work. See Errors.

Bash
curl -X POST "https://dashboard.steamlabs.dev/api/v1/accounts/refresh-details" \
  -H "Authorization: Bearer $STEAMLABS_API_KEY" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{"filters":{"details_never_refreshed":true}}'
JSON
{ "task_id": "019fb440-…", "accounts_affected": 8412, "accounts_skipped": 0, "queued": true }

task_id is the batch parent. Poll it through the tasks API to follow the run. accounts_skipped counts accounts left out as not eligible, most often because they already had a task of that type pending (which is what stops a repeated call from double-queueing anyone). When anything was skipped, accounts_skipped_reasons splits the count by reason, using the same slugs as task creation: task_in_flight here, plus never_refreshed on check-bans for accounts with no Steam64 id yet.

set-profile takes mode (manual, profile, group) plus the matching fields: persona_name, custom_url, summary, country for manual, profile_id for a library profile, profile_group_id and allow_reuse for a group. Avatars come from library profiles; there is no upload here. custom_url is the Steam vanity slug (steamcommunity.com/id/{slug}): letters, numbers and underscores, 2 to 32 characters. Omit it or send null to leave each account's current custom URL alone. Steam rejects a slug that is already taken. Library profiles send their slug only on the first-ever apply (times_used is 0). Reuse (one profile on many accounts, or a group with allow_reuse) leaves later accounts' custom URLs alone.

change-profile-privacy takes any of profile, owned_games, inventory, inventory_gifts, playtime, friends_list (1 private, 2 friends only, 3 public) and comment_permission (0 friends, 1 public, 2 private). Omitted settings are left untouched, so one call can flip a single setting across the whole fleet. Sending none of them is refused.

Edit many accounts

These change our own records rather than anything on Steam, so no task is created. With account_ids they apply immediately and answer 200; with filters they are queued and answer 202.

Endpoint Body Effect
POST /api/v1/accounts/assign-proxies proxy_ids Replaces the pinned proxies. An empty list clears them
POST /api/v1/accounts/distribute-proxies proxy_ids One proxy per account, round-robin
POST /api/v1/accounts/assign-proxy-groups proxy_group_ids Replaces the pinned groups
POST /api/v1/accounts/clear-proxies None Drops account-level pins and groups. Proxy groups linked on the account group still apply
POST /api/v1/accounts/add-tags tag_ids Adds tags without disturbing existing ones
POST /api/v1/accounts/remove-tags tag_ids Removes only those tags
POST /api/v1/accounts/set-group group_id Moves every selected account to one group. Send null for Ungrouped
POST /api/v1/accounts/clear-stored-session None Forgets our saved login locally (nothing is revoked on Steam)
POST /api/v1/accounts/generate-billing-address country_source Generates a billing address for market buy orders
DELETE /api/v1/accounts/bulk None Deletes the selection
JSON
{ "accounts_affected": 240, "queued": false }

A bulk delete keeps back the accounts that cannot go yet (an AssetPay pool sale still in trade protection, as above) and deletes the rest. When it does, the answer says so and accounts_affected counts only what really went:

JSON
{ "accounts_affected": 239, "queued": false, "kept_in_trade_protection": 1 }

country_source picks which signal the country comes from: auto (default), store_country, wallet_currency or login_country. Accounts whose chosen source is empty are skipped rather than given a default-country address, because a wrong country fails at Steam's checkout instead of here.

clear-stored-session is the local half of the pair. sign-out-everywhere is the one that talks to Steam and revokes every device.

Games, packages and ban history

GET/api/v1/accounts/{id}/games

The account's Steam library, most played first.

API key required
GET/api/v1/accounts/{id}/packages

The account's Steam licenses and the apps they grant.

API key required
GET/api/v1/accounts/{id}/bans

Every ban we have detected on the account, newest first.

API key required

These are separate endpoints rather than fields on the account because each is unbounded: a well-stocked account owns hundreds of games. games takes search, sort (playtime_forever_minutes, playtime_two_weeks_minutes, last_played_at, name, app_id) and direction.

The account's current ban state is on the record itself, under bans. The sub-resource is the timeline of detections.

Tags

GET/api/v1/account-tags

Your tags, with how many accounts carry each.

API key required
POST/api/v1/account-tags

Create a tag.

API key required
PATCH/api/v1/account-tags/{id}

Rename or recolor a tag.

API key required
DELETE/api/v1/account-tags/{id}

Delete a tag. Accounts keep everything except the label.

API key required

Names are unique per account holder, ignoring case, so Prime and prime cannot both exist and become two badges nobody can tell apart. color is an optional six-digit hex; leave it out and the tag takes a colour derived from its own name, so the same word always lands on the same badge.

Attaching tags to accounts is not done here. It is a bulk operation on a selection of accounts, so it lives with the other account edits: add-tags and remove-tags above.

Account groups

Groups are exclusive. Each account belongs to one group or is Ungrouped. Deleting a group keeps its accounts and makes them ungrouped.

You can attach proxy groups with proxy_group_ids on create or update. Members then log in through those groups, plus any proxies or proxy groups pinned on the account itself. Moving an account out of the group drops the inherited groups. Its own pins stay. proxies.group_count on an account is still only the groups pinned on that account.

GET/api/v1/account-groups

Your account groups, including account counts and linked proxy groups.

API key required
POST/api/v1/account-groups

Create an account group, optionally with linked proxy groups.

API key required
GET/api/v1/account-groups/{id}

One account group.

API key required
PATCH/api/v1/account-groups/{id}

Rename an account group, or change its linked proxy groups.

API key required
DELETE/api/v1/account-groups/{id}

Delete a group and leave its accounts ungrouped.

API key required

Names are unique per account holder, ignoring case. Responses include accounts_count and proxy_groups (id, name). Omit proxy_group_ids on PATCH to leave the link unchanged. Send an empty list to clear it.