Skip to content
SteamLabs API

Endpoints

Tasks

Queue any of the 47 task types across your fleet, then follow, retry, or cancel the work.

Tasks are what the platform actually does. Everything else (accounts, proxies, profiles, integrations) exists so that a task can run. One endpoint creates them, the rest let you watch, retry, and stop them.

Two scopes cover the domain. tasks.read for the catalogue, the listings and a single task, tasks.write for creating, retrying, cancelling and deleting. POST /api/v1/tasks/preview also needs tasks.write even though it writes nothing, so a read-only key cannot size up a selection.

Start at the task catalogue. It tells you the 47 types you may create, the exact config each one accepts, and which of them your plan covers. A client that guesses instead will cache the wrong shapes and find out through 422.

Selecting accounts

Creating tasks and previewing a selection both take a selection: which Steam accounts the work targets. There are two shapes, and you send exactly one.

JSON
{ "account_ids": ["019fb42e-9a61-70d2-818a-f6a56593f3a5", "019fb42e-9a7e-728d-b960-8b4c2162898c"] }

An explicit list, capped at 1,000 ids. Over the cap you get 422 bulk_limit_exceeded with the cap in max.

JSON
{ "filters": { "guard": "mobile", "wallet": "has" }, "exclude_account_ids": ["019fb42e-9a7e-728d-b960-8b4c2162898c"] }

A definition resolved on the server. The ids are never materialized, so "every account with a mobile authenticator" is the same size on the wire whether it names four accounts or forty thousand. A filter selection always runs asynchronously and always answers 202, even when it happened to resolve small enough to run inline.

Sending both is refused, and so is sending neither: a request with no selection would otherwise mean "every account I own" by accident.

Filter keys

Key Values
search Username, persona name, or Steam64 id
guard mobile, email, none
wallet has, none
details refreshed, never
market eligible, restricted, unknown
csfloat ready
inventory synced, never
session online, offline
tags Array of tag ids. Matches an account carrying any of them
exclude_tags Array of tag ids. Drops an account carrying any of them
groups Array of account group ids. Matches an account in any selected group
include_ungrouped Boolean. Includes accounts with no group and is ORed with groups
capability Array of sell_steam, sell_csfloat, sell_marketcsgo, place_buy_orders. Matches any of them
wallet_currency A wallet currency code, for example EUR. Send none to match accounts Steam has not opened a wallet for yet
wallet_min, wallet_max Numeric balance bounds
points_min, points_max Steam Points balance bounds. Accounts whose Points balance has never been refreshed match neither bound
external_funds_min, external_funds_max Steam External Funds Used bounds, in USD. Accounts whose spend has never been refreshed match neither bound
exclude_account_ids Ids to drop from the match, capped at 1,000. Only meaningful with filters

wallet_min and wallet_max compare natively when wallet_currency is also set. Without it, balances are normalized to USD, because a bare number would otherwise compare 15 PLN against 15 EUR. An account whose balance has never been reported matches neither bound.

session is read from the live worker registry rather than a column. While no worker is reporting, the filter is ignored rather than matching nothing.

search, session, tags, exclude_tags, groups, and include_ungrouped are spelled the same in both vocabularies. For the other account-side filters, use GET /api/v1/accounts to get the ids and pass them as account_ids.

Task lifecycle

A task moves through six statuses.

Status Meaning
Waiting Created but held back, waiting for fleet capacity. Large batches start here
Queued Handed to the worker fleet, not picked up yet
Running A worker has it
Completed Finished, and it did what it was asked
Failed Finished with an error. error says what went wrong
Cancelled Stopped before it finished

The last three are final. Nothing leaves them.

Parents and children

How many accounts are eligible decides the shape of what you get back.

  • One eligible account: a single standalone task, bound to that account. 201 with the task record.
  • Many eligible accounts: a batch parent with one child task per account. The parent has no steam_account_id of its own and carries the rolled-up counters in children.
  • More than 500 eligible accounts, or any filter selection: the same batch parent, but built by a queued job. 202 with just the parent's id.

"Eligible" excludes accounts that already have a pending, queued or running task of the same type, which is what stops a repeated call from double-queueing anyone. It also excludes accounts a type cannot work on: check_bans skips accounts with no steam64_id, assign_currency skips accounts that already have a wallet, place_buy_order skips accounts that cannot fund the order, change_password skips accounts missing a stored Steam password or either authenticator secret. Those show up as accounts_skipped.

When anything was skipped, the response also carries accounts_skipped_reasons: an object of reason slug to account count. An account failing several checks is counted once, against the first check it failed, so the counts sum to accounts_skipped (any remainder a reason could not be pinned on is simply not listed). The slugs are stable:

Slug The skipped accounts
task_in_flight Already have a pending, queued or running task of this type (for withdraw_csfloat_balance, any CSFloat task)
nothing_to_list No inventory items matching the sell config (sell_items, any marketplace)
no_authenticator No mobile authenticator, so listings cannot be confirmed (sell_items on Steam)
market_restricted Known Community Market restriction (sell_items on Steam)
not_ready_csfloat Not connected and enabled for CSFloat, or no mobile authenticator
not_ready_marketcsgo Not connected and enabled for Market.CSGO, no dedicated proxy, or no mobile authenticator
no_matching_units No storage units (or none matching the name) with anything to move (storage types)
insufficient_funds Cannot fund price x quantity on top of open orders, or in max_affordable mode cannot pay for one unit (place_buy_order); last synced wallet balance below max_total (buy_market_listing)
cannot_place_buy_orders Missing market access, mobile authenticator, or billing address (place_buy_order, buy_market_listing)
nothing_to_cancel Nothing open on the account matches the conditions (cancel_buy_orders, cancel_market_listings in matching mode)
no_wallet_currency Steam has not opened a wallet for the account, so there is no currency to pay in. An assign_currency task opens one for free (add_funds_paysafecard, add_funds_blik)
unsupported_wallet_currency Wallet currency the payment method does not support (add_funds_paysafecard in tier mode; add_funds_blik outside PLN)
topup_already_pending A wallet top-up is still awaiting payment; Steam cancels it if a new one is created, so pay or discard it first (add_funds_paysafecard, add_funds_blik)
never_refreshed No steam64_id on record yet (check_bans)
already_claimed Nothing left to claim: the account already has these games, or Steam does not offer them in its region (claim_free_games)
missing_base_game What is left to claim is DLC whose base game the account does not own (claim_free_games)
wallet_already_assigned Already have a wallet currency (assign_currency)
no_eur_country From a country without a EUR store, asked for EUR with no country chosen (assign_currency)
no_stored_password No Steam password on file (change_password)
no_shared_secret No shared secret, so Steam Guard cannot log in or confirm (change_password)
no_identity_secret No identity secret, so the recovery confirmation cannot be accepted (change_password)
not_connected_csfloat No enabled CSFloat connection with an API key and a dedicated proxy, or a key from a different Steam account (withdraw_csfloat_balance)
insufficient_csfloat_balance Last synced CSFloat balance cannot cover the withdrawal (withdraw_csfloat_balance)

The field is absent when nothing was skipped, and on the few pipelines that do not attribute their skips (distribution, boosting, trade-ups).

Following a fan-out

Bash
curl "https://dashboard.steamlabs.dev/api/v1/tasks/019fb431-2c88-71ea-b0a3-8ce2f5d19b07" \
  -H "Authorization: Bearer $STEAMLABS_API_KEY"

Poll the parent. It sits at Queued while its children are still being written, flips to Running once they all exist, and finishes when the last child does. children.total grows as the batch is built, so treat completed + failed + cancelled == total as done rather than watching total alone.

For per-account detail, use GET /api/v1/tasks/{id}/children. Do not page the whole child set just to count outcomes: the parent's counters already have them.

Discover what you can create

Every creatable type, its config schema, and whether your plan covers it.

GET/api/v1/task-types

The task catalogue: 47 types, their config shapes, and your plan's access to each.

API key required

Requires tasks.read.

Parameter Type Description
page integer Page number, default 1
per_page integer Rows per page, default 50, clamped to 200. All 47 types fit on the first page
Bash
curl "https://dashboard.steamlabs.dev/api/v1/task-types" \
  -H "Authorization: Bearer $STEAMLABS_API_KEY"
JSON
{
    "data": [
        {
            "type": "claim_weekly_drops",
            "label": "Claim Weekly Drops",
            "description": "Claim each account's two weekly CS2 drops, picked automatically by highest price or from your selection.",
            "category": "cs2",
            "requires_steam_account": true,
            "allowed": true,
            "blocked_by": null,
            "plan": null,
            "config_schema": {
                "strategy": {
                    "type": "string",
                    "required": true,
                    "enum": ["highest_price", "manual"],
                    "default": "highest_price"
                },
                "ignore_graffitis": { "type": "boolean", "required": false, "default": false },
                "claim_item_ids": {
                    "type": "array",
                    "required": false,
                    "max": 2,
                    "default": [],
                    "items": { "type": "string", "required": false, "max": 64 }
                }
            }
        },
        {
            "type": "trade_up_contract",
            "label": "Trade-Up Contract",
            "description": "Craft 10 same-rarity skins into 1 skin of the next rarity, picked automatically by expected value or by hand.",
            "category": "cs2",
            "requires_steam_account": true,
            "allowed": false,
            "blocked_by": "plan",
            "plan": {
                "heading": "Not included in your plan",
                "badge": "Available on Pro or higher",
                "upgrade_url": "https://dashboard.steamlabs.dev/subscription"
            },
            "config_schema": { "…": {} }
        }
    ],
    "meta": { "page": 1, "per_page": 50, "total": 33, "last_page": 1 }
}

Locked types are listed, not hidden, exactly as the panel greys them instead of dropping them. allowed is false and plan carries the same block a 403 plan_limit_reached would return, so an integration can tell "you have not bought this" apart from "this does not exist".

blocked_by says why a locked entry is locked: plan (buy more plan) or maintenance (wait it out). During platform maintenance every entry reports allowed: false with blocked_by: "maintenance", and the plan block's upgrade_url is null because no purchase lifts it.

category is one of account, profile, trading, inventory, market, cs2, tf2. requires_steam_account is false for the four orchestration types (distribute_items, store_items, withdraw_items, sell_items), whose parents group per-account children rather than reaching a worker themselves.

Reading a config schema

Each field in config_schema is described with the same descriptor the server validates against, so the two cannot drift.

Key Meaning
type string, integer, number, boolean, array, or object
required Always required
required_if { "field": "mode", "values": ["manual"] }: required when that sibling matches
enum The allowed values
min, max A numeric bound for numbers, a length for strings, an item count for arrays
format An extra format rule, uuid or url
default The panel's default, published so you can mirror it
items The element shape of an array. For arrays of objects, items.properties holds the per-object fields

Fields that are not required accept null, which means the same as leaving the key out.

List tasks

GET/api/v1/tasks

Your tasks, newest first, paginated and filterable.

API key required

Requires tasks.read.

Parameter Type Description
status string One status: pending, queued, running, completed, failed, cancelled
type string One task type value, for example sell_items
parent uuid Only children of this batch parent
top_level boolean true for parents and standalone tasks only, hiding batch children
account_id uuid Only tasks bound to this Steam account
search string Username, persona name or Steam64 id. Matches the task's own account, or for a batch parent any of its children's
origin string Where the task was started from: manual, api, workflow, automatic, retry, extension. See Where a task came from
origin_source string For automatic tasks, which SteamLabs process started it, for example inventory_refresh. Usable on its own
created_after date Tasks created at or after this timestamp
created_before date Tasks created at or before this timestamp
sort string created_at (default), started_at, finished_at
direction string asc, desc (default)
page, per_page integer Default 50 per page, clamped to 200
Bash
curl "https://dashboard.steamlabs.dev/api/v1/tasks?top_level=true&status=running&per_page=1" \
  -H "Authorization: Bearer $STEAMLABS_API_KEY"
JSON
{
    "data": [
        {
            "id": "019fb431-2c88-71ea-b0a3-8ce2f5d19b07",
            "type": "refresh_details",
            "status": "running",
            "is_batch_parent": true,
            "is_cancelling": false,
            "parent_id": null,
            "steam_account_id": null,
            "steam_account": null,
            "children": { "total": 4182, "completed": 3907, "failed": 12, "cancelled": 0 },
            "origin": {
                "kind": "workflow",
                "label": "Automation",
                "detail": "Nightly refresh",
                "source": null,
                "source_label": null,
                "workflow_id": "019fb3f0-5e2a-7d1c-9c44-0f8a2b6d7e11",
                "workflow_run_id": "019fb431-2b70-7a05-8d2e-6c1f0a9b3c22",
                "api_key_id": null,
                "api_key_name": null,
                "retried_task_id": null
            },
            "error": null,
            "worker_id": null,
            "bytes_sent": null,
            "bytes_received": null,
            "cancel_requested_at": null,
            "dispatch_after": null,
            "started_at": "2026-07-30T14:02:11+00:00",
            "finished_at": null,
            "created_at": "2026-07-30T14:01:58+00:00",
            "updated_at": "2026-07-30T14:19:40+00:00"
        }
    ],
    "meta": { "page": 1, "per_page": 1, "total": 1, "last_page": 1 }
}

Where a task came from

Every task records what started it, at the moment it is created, and origin carries that on every task record (list rows, the single task, children, and the record a create or retry returns). Children of a batch carry their parent's origin.

kind Started by What detail names
manual Someone clicking in the dashboard Nothing
api A user API key The key's name, with api_key_id and api_key_name filled in
workflow An automation run The automation's name, with workflow_id and workflow_run_id filled in
automatic One of SteamLabs' own processes The process, with source and source_label filled in
retry A retry of an earlier task, from the dashboard or POST /api/v1/tasks/{id}/retry The first characters of the retried task's id, with retried_task_id filled in. A retry asked for over the API also carries api_key_id and api_key_name
extension The browser extension The paired browser's name

source is one of inventory_refresh, sold_item_refresh, steam_market_sync, cancel_unsold_listings, listing_repricing, listing_confirmation, trade_offer_sync, trade_auto_accept, marketplace_sync, assetpay_sync, assetpay_delivery, assetpay_instant_sell, webhook_notification, wallet_payment_recheck, task_follow_up, farmlabs. New processes may be added; treat an unfamiliar value as "a SteamLabs process" rather than an error.

Names (detail, api_key_name, source_label) are snapshots from when the task was created and are written in the dashboard's language. A renamed key or a deleted automation keeps the name it had. Branch on kind, source and the ids, never on the labels.

origin is null on tasks created before origin tracking existed. The dashboard shows those as "Unknown". There is no filter value for them; origin and origin_source match stamped tasks only.

bytes_sent and bytes_received are what the worker measured on the wire while running the task. They are null when it reported nothing, which is not the same as zero, so skip those rows rather than coerce them when you add figures up. A task that was re-queued reports only what its final attempt moved. The same bytes roll up per proxy, account and source under bandwidth usage.

task_data and logs are deliberately absent from list rows. A storage batch's payload carries thousands of asset ids and a finished task's log is unbounded, so a 200-row page would be megabytes nobody asked for. Both are on the single-task response.

top_level=true is the filter you want for a dashboard. Without it, a 100,000-account batch puts 100,000 children in your list.

Preview a selection

Resolve a selection and count it, without creating anything.

POST/api/v1/tasks/preview

How many accounts a selection resolves to. Writes nothing.

API key required

Requires tasks.write. No Idempotency-Key: it writes nothing, so a retry costs one COUNT(*).

Takes a selection and nothing else. No type, no config.

Bash
curl -X POST "https://dashboard.steamlabs.dev/api/v1/tasks/preview" \
  -H "Authorization: Bearer $STEAMLABS_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"filters": {"guard": "mobile", "market": "eligible"}}'
JSON
{ "accounts_affected": 4182 }

The count is the selection, not the eligible set. POST /api/v1/tasks will usually create fewer tasks than this, because accounts that already have a task of the same type queued are skipped.

Cancel many tasks

POST/api/v1/tasks/cancel

Ask a list of tasks to stop.

API key required

Requires tasks.write.

Field Type Description
task_ids array Task ids, at least one, capped at 1,000

Explicit ids only. There is no filter mode here on purpose: these act on task rows you have just listed and can see, and "cancel everything queued" is a blast radius you should have to spell out.

Bash
curl -X POST "https://dashboard.steamlabs.dev/api/v1/tasks/cancel" \
  -H "Authorization: Bearer $STEAMLABS_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"task_ids": ["019fb431-2c88-71ea-b0a3-8ce2f5d19b07", "019fb431-3d0f-7002-9a41-51c8b6f7e2aa"]}'
JSON
{ "cancelled": 1, "skipped": 1 }

skipped covers everything that did not move: tasks that finished between your listing and this call, tasks already cancelling, children of a live batch (cancel the parent instead), and ids that are not yours.

Create tasks

The endpoint everything else on this page exists to support.

POST/api/v1/tasks

Create tasks of one type across a selection of accounts.

API key required

Requires tasks.write. Requires an Idempotency-Key header. See Bulk operations.

Field Type Description
type string One of the 33 values from GET /api/v1/task-types. Anything else is refused
config object The type's own options. Shape depends on type, see Config by task type
account_ids array Explicit account ids, capped at 1,000
filters object A filter selection instead of ids
exclude_account_ids array Ids to drop from a filter selection, capped at 1,000
Bash
curl -X POST "https://dashboard.steamlabs.dev/api/v1/tasks" \
  -H "Authorization: Bearer $STEAMLABS_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{
        "type": "sell_items",
        "config": { "marketplace": "steam", "mode": "undercut", "undercut_cents": 1, "categories": ["container"], "max_items": 50 },
        "filters": { "market": "eligible", "inventory": "synced" }
      }'

What you get back

201 Created, when explicit ids resolved to 500 or fewer eligible accounts. The task record (a standalone task, or the batch parent) plus two counters:

JSON
{
    "id": "019fb440-7a10-7c33-bd51-2f0c9e4471d2",
    "type": "refresh_details",
    "status": "queued",
    "is_batch_parent": false,
    "is_cancelling": false,
    "parent_id": null,
    "steam_account_id": "019fb42e-9a61-70d2-818a-f6a56593f3a5",
    "steam_account": {
        "id": "019fb42e-9a61-70d2-818a-f6a56593f3a5",
        "username": "farm_017",
        "persona_name": "Ada",
        "steam64_id": "76561198000000017"
    },
    "children": { "total": 0, "completed": 0, "failed": 0, "cancelled": 0 },
    "origin": {
        "kind": "api",
        "label": "API",
        "detail": "Fleet scripts",
        "source": null,
        "source_label": null,
        "workflow_id": null,
        "workflow_run_id": null,
        "api_key_id": "019fb3c2-11aa-7f00-8a3b-5d6e7f8a9b0c",
        "api_key_name": "Fleet scripts",
        "retried_task_id": null
    },
    "error": null,
    "worker_id": null,
    "bytes_sent": null,
    "bytes_received": null,
    "cancel_requested_at": null,
    "dispatch_after": null,
    "started_at": null,
    "finished_at": null,
    "created_at": "2026-07-30T14:02:11+00:00",
    "updated_at": "2026-07-30T14:02:11+00:00",
    "tasks_created": 1,
    "accounts_skipped": 0
}

A task created here is stamped origin.kind: "api" with the name of the key that made the call, so it can be told apart from dashboard clicks and automations later (see Where a task came from).

202 Accepted, for every filter selection and for any batch over 500 eligible accounts. The parent exists, its children are being written by a queued job:

JSON
{
    "task_id": "019fb431-2c88-71ea-b0a3-8ce2f5d19b07",
    "accounts_affected": 4182,
    "accounts_skipped": 311,
    "accounts_skipped_reasons": { "task_in_flight": 292, "never_refreshed": 19 }
}

200 OK, only for a trade-up dry run (config.trade_up_dry_run: true). Guardrails were evaluated, nothing was created and nothing was consumed:

JSON
{
    "dry_run": { "contracts": 42, "slots": 378, "cost": 118.44, "ev": 131.02, "scanned": 100, "total": 4182 }
}

The dry run scans at most the first 100 accounts of the selection, so scanned and total differ on anything large.

When nothing is created

Status Code Why
422 no_eligible_accounts Every account in the selection already has a task of this type queued, or none met the type's requirements. Adds accounts_skipped, plus accounts_skipped_reasons saying which requirement each account failed
422 nothing_to_plan The selection was fine, but the planner matched no items. Only for distribute_items, sell_items and the storage types
422 proxies_required You own no proxies and hold more accounts than the shared pool covers, so these logins would be refused anyway
403 plan_limit_reached Your plan's allowed_task_types does not cover this type, or its allowed_marketplaces does not cover the venue
503 maintenance_mode Platform maintenance pauses new tasks. Adds reason. Transient, see Errors

The two 422s are different answers and worth telling apart. no_eligible_accounts means "pick different accounts"; nothing_to_plan means "the accounts were fine, their inventories were not".

plan_limit_reached is not transient, and retrying never fixes it. GET /api/v1/task-types tells you which types are locked before you ask, and GET /api/v1/me reports allowed_task_types and allowed_marketplaces outright. The marketplace half bites sell_items (per its config.marketplace), place_buy_order and buy_market_listing (always Steam).

What Steam is giving away

The giveaway catalogue behind claim_free_games: the paid games Steam is currently handing out permanently. Refreshed hourly from Steam's own store feed.

GET/api/v1/free-games

Steam games that are currently free to keep.

API key required

Requires account.read, not tasks.read: this describes what Steam is offering, not anything you own or have queued.

Query Type Description
type string Only game, dlc or other (soundtracks, software and other extras)
requires_base_game boolean 0 for standalone games only (the panel's "Hide DLC" filter), 1 for DLC and other add-ons only
per_page integer Page size
Bash
curl "https://dashboard.steamlabs.dev/api/v1/free-games?requires_base_game=0" \
  -H "Authorization: Bearer $STEAMLABS_API_KEY"
JSON
{
    "data": [
        {
            "app_id": 2000040,
            "name": "Space Menace",
            "type": "game",
            "requires_app": null,
            "package_id": 1827145,
            "header_image": "https://shared.akamai.steamstatic.com/store_item_assets/steam/apps/2000040/header.jpg",
            "normal_price_cents": 699,
            "ends_at": "2026-09-23T17:00:00+00:00",
            "store_url": "https://store.steampowered.com/app/2000040/"
        }
    ],
    "meta": { "page": 1, "per_page": 50, "total": 1, "last_page": 1 }
}
Field Type Description
app_id integer The Steam app id, which is what a claim_free_games config takes
name string The game's store name
type string game, dlc or other, as Steam types the app. null until the hourly sync has read it
requires_app object For DLC and other add-ons, the base game an account must own to claim it: app_id, name and free_to_play. null for a standalone game
package_id integer The promotional package Steam offered our catalogue read. Informational: each account's own store page decides what it is actually offered
normal_price_cents integer What the game normally costs, always in USD so the figure means the same thing for every caller
ends_at string When the giveaway closes, or null when Steam does not publish a deadline we can read
store_url string The store page

Most of the time this list is short, and often it is empty: giveaways come and go a few times a month. The list is read from one region, so an entry is an offer worth trying rather than a promise that a given account can take it.

You do not have to wait for this catalogue to notice a promotion. A claim_free_games task accepts any app id, and each account checks its own store page, so nothing is claimed unless Steam actually offers it there.

Config by task type

config is validated against the type you sent. Every type below is creatable; the shapes here are the same descriptors GET /api/v1/task-types publishes.

Throughout: min and max are a numeric bound on numbers, a character length on strings, and an item count on arrays. Fields not marked required may be omitted or sent as null, which mean the same thing.

Item conditions

Four types narrow which items they act on with the same shared block: sell_items, distribute_items (also inside each routing rule), store_items and withdraw_items.

Field Type Description
games array cs2, tf2, steam. Absent on the storage types, whose units only ever hold CS2 items
price_min number Lowest per-item price to include
price_max number Highest per-item price to include
item_ids array Specific item ids, strings up to 191 characters
categories array weapon, knife, gloves, container, sticker, graffiti, charm, patch, agent, music_kit, collectible, tool, key, pass, gift, trading_card, booster_pack, emoticon
origins array Steam item origin ids, integers
float_min number sell_items only. Lowest CS2 paint wear to include, 0 to 1. Items without a float are skipped
float_max number sell_items only. Highest CS2 paint wear to include, 0 to 1

Leave the whole block out and the type acts on everything it can. That is fine for a storage deposit and expensive for a sale, which is why sell_items demands an explicit acknowledgement (see below). float_min and float_max are CS2-only and are refused when games is set and does not include cs2.

No configuration

These take no config at all. Send {} or leave the key out.

Type What it does
refresh_inventory Logs in and refreshes only the account's inventories, much lighter than a full details refresh
refresh_wallet Logs in and refreshes only the wallet balance, pending funds and currency, much faster than a full details refresh
refresh_point_shop_balance Logs in and refreshes the Points Shop balance and lifetime totals
login Signs in and leaves the session open, so later tasks skip the login
sign_out_everywhere Deauthorizes every Steam session on every device, ours and FarmLabs' included
check_bans Reads VAC, community, game and trade ban state. No login needed. Accounts with no Steam64 id are skipped
fetch_trade_url Re-reads the trade URL token and overwrites the stored one
sync_market_listings Pulls current listings, buy orders and wallet balance
sync_market_history Pulls sales, purchases and cancelled listings
fetch_armory_progress Launches CS2 and reads pass stars, XP, credits, medal state and rank
fetch_weekly_drops Launches CS2 and reads claimable weekly drops
clear_armory_passes Removes completed armory passes to free pass slots
claim_service_medal Claims the year's service medal where the rank allows it
claim_community_badge Completes the free Steam community-badge quests Steam allows without spending wallet or points (Community Pillar and later levels of the same badge)

Account and wallet

refresh_details logs in and refreshes everything we know about the account. Every group of reads is on by default; send scopes to run only the groups you need. Fewer groups means a faster, lighter refresh, because the worker also skips the Steam session a skipped group would have needed (the community web session, the CS2 game coordinator, the TF2 game coordinator).

Field Type Description
scopes array Which groups to refresh, from the list below. Omitted means every group. An empty list is refused: for a bare login, use login

The login itself always brings the account basics, whatever you send: Steam64 id, wallet balance and currency, persona name, country, email, Steam Guard state, avatar and a fresh login token.

Scope What it refreshes
wallet When pending wallet funds become available
profile Privacy settings, whether the profile is set up, store country, equipped profile items
points Points Shop balance and lifetime totals
spend External Funds Used. Read only when the login was a password sign-in (for example the first refresh after adding the account), since Steam hides the page from a saved login. Use refresh_external_funds to read it any time
market Market eligibility, listings, buy orders and history
inventory Every game inventory, plus CS2 armory progress and weekly drops
ownership Owned games
tf2 TF2 premium status, backpack slots and trade ban
trades Incoming and outgoing trade offers, and the trade URL
bans VAC, game and community bans
JSON
{
    "type": "refresh_details",
    "filters": { "tags": ["selling"] },
    "config": { "scopes": ["market", "trades"] }
}

The created task's task_data carries the list when it was narrowed and is null for a full refresh, so a retry repeats the same groups. POST /api/v1/accounts/refresh-details is always the full refresh; create a task here when you want a narrower one. refresh_inventory, refresh_wallet and refresh_point_shop_balance remain as one-purpose shortcuts.

refresh_external_funds reads Steam's External Funds Used. Steam only shows that page shortly after the password was typed, and a saved login never counts, so by default each account signs in with its password and Steam Guard code the way a browser does, reads the figure and signs out again. The saved login is not touched.

Field Type Description
password_sign_in boolean Default true. Needs the account's stored password and Steam Guard secret; an account without them fails with the reason. false reads through the saved login instead, which only works within a few hours of the account's last password sign-in and fails otherwise

The figure lands on the account's spend block (see Accounts). Password sign-ins count toward each proxy's login limit, so a large batch queues rather than tripping Steam's throttle.

change_password sets a new Steam password on every eligible account through the logged-in help wizard and a mobile Steam Guard confirmation. Accounts without a stored password, shared secret, or identity secret are skipped. After Steam accepts the change, the stored password is updated and the session is cleared so the next task logs in fresh.

Two modes. Omit mode (or send password) to type one password for every account. Send mode: generate to mint a unique password per account at create time. Read a generated password from an account export or from the account Credentials tab after the task succeeds. The task itself never returns it.

Field Type Description
mode string password (default) or generate. Omitted mode is password, so existing clients keep working
password string Required when mode is password or omitted. The new Steam password, 8 to 64 printable ASCII characters. Steam still rejects common or leaked passwords when the task runs. Ignored when mode is generate. Never returned on task reads
length integer Generate only. 8 to 64, default 16
lowercase boolean Generate only. Default true
uppercase boolean Generate only. Default true
digits boolean Generate only. Default true
symbols boolean Generate only. Default false. Printable ASCII punctuation, no smart quotes

At least one of lowercase, uppercase, digits, or symbols must be true when generating. The password is stored only so the worker can apply it. GET /api/v1/tasks/{id} omits it from task_data, including on a batch parent.

request_free_licenses registers the free licence for the games you name.

Field Type Description
mode string all (default) or random. Omitted mode is all, so existing clients keep working
app_ids array Steam app ids, 1 to 200 of them, each a positive integer
random_count integer Random mode only. How many games to register per account, 1 to the number of app_ids you sent. Default 5

In all mode every account registers every app id you sent. In random mode each account draws its own random_count games from that pool, so a fleet ends up with varied libraries rather than one list repeated across every account. The draw happens when the tasks are created, so each child task already names the games it will register and a retry repeats that account's own draw. Games an account does not already own are drawn first, so the count delivers new licences wherever there are new licences left to give.

This is the free-to-play door: it registers a free-on-demand licence, which works for permanently free titles such as CS2, Dota 2 or Team Fortress 2. It does not claim a limited giveaway. Steam accepts the request for one and grants nothing, without an error. Use claim_free_games for those.

claim_free_games claims the paid games Steam is currently giving away permanently, the store's one-click "Add to Account" button.

Field Type Description
mode string picked (default) or all_active. Omitted mode is picked
app_ids array Required when mode is picked or omitted. Steam app ids, 1 to 25 of them, each a positive integer

all_active claims every giveaway running at the moment the task is created, read from the giveaway catalogue. The list is resolved to concrete app ids before anything is queued, so the created task names the games it actually tried, and retrying it later claims that same set rather than whatever is free by then.

Per account, each game ends up in exactly one of four buckets, reported in the finished task's task_data:

Bucket Meaning
claimed Added to the account. Each entry carries app_id and the granted package_id
already_owned The account already had it, so nothing was posted. A list of app ids
unavailable Steam offered the account no giveaway for that app: wrong region, the promotion ended, or a DLC whose base game the account does not own. Each entry carries app_id and a reason
failed Steam refused the claim. Each entry carries app_id and Steam's own message

unavailable is not an error. Giveaways are regional, so on a fleet whose proxies exit in different countries a real share of accounts land there, and those accounts are never asked about that game again. failed is treated as transient and can be retried.

Accounts with nothing left to do are skipped rather than queued, and reported under the already_claimed skip reason.

DLC and other add-ons (anything with a requires_app in the catalogue) can only be claimed by an account that owns the base game. When an account's synced library does not have a paid base game, the add-on is left out of that account's task, and an account left with nothing else to claim is skipped under missing_base_game. Nothing is recorded for that skip, so a later task picks the add-on up once the account owns the game. A free-to-play base game, or an account whose library has never been synced, is checked by the account itself on Steam instead, and lands in unavailable with a reason starting needs the base game when the game is missing. Such an add-on is asked about again once the account owns the base game.

redeem_wallet_code redeems Steam wallet codes, one code per account.

Field Type Description
wallet_codes array 1 to 10,000 codes, each 3 to 64 characters

The batch is capped at the number of codes you sent: 40 codes across a 600-account selection creates 40 tasks and reports the rest as skipped. Codes are single use, which is the reason this endpoint carries an Idempotency-Key.

add_funds_paysafecard starts a wallet top-up and returns a payment link to finish off-site.

Field Type Description
mode string tier (default) or exact
tier string tier_5, tier_10, tier_15, tier_25 (default), tier_50, tier_100. Required when mode is tier
amount integer 1 to 1,000,000, in the account's own wallet currency. Required when mode is exact. Default 5

A tier maps to the nearest denomination in each account's currency, so accounts in a currency with no denomination table are skipped rather than dispatched amountless.

Both modes need the account to already hold a wallet currency. Steam assigns one when the account's wallet is first opened, not from its store country, so an account that has never been funded has no currency to pay in even though the store shows prices in one. Those accounts are skipped as no_wallet_currency in either mode; run an assign_currency task on them first, which opens the wallet for free, then top up. You can list them with the account filter wallet_currency: "none".

add_funds_blik starts a Steam wallet top-up via Blik and returns a payment link to finish off-site. Blik is PLN only, so there are no tiers: one exact amount, applied to every selected account.

Field Type Description
amount integer 1 to 1,000,000, in Polish zloty. Default 20

Accounts whose wallet currency is not PLN are skipped rather than dispatched. The wallet payment this produces has provider blik; open its pay_url (see the wallet payments endpoint) to complete the payment.

buy_package buys a Steam store package with the account's wallet balance: a named package, or a random game the account does not own yet.

Field Type Description
package string cs2_prime (default), custom, random_store or random_list
custom_package string Required when package is custom. A numeric package id, or a https://store.steampowered.com/app/… or /sub/… URL. Max 255 characters
max_price number Required for random_store and random_list. The most an account may pay, in its own wallet currency, with no conversion, 0.01 to 1,000,000. Nothing above it, or above the wallet balance, is ever bought
min_reviews integer random_store only. Only games with at least this many Steam user reviews, 0 to 10,000,000. Omit for any
min_review_score integer random_store only. Only games whose positive review share is at least this percentage, 0 to 100. Omit for any
package_list array Required when package is random_list. 1 to 200 entries, each a https://store.steampowered.com/app/… URL, a /sub/… URL or a numeric package id (max 255 characters each). Duplicates collapse to their first position
pick_order string random_list only. random (default) shuffles the list per account; list_order buys the first entry the account does not own and can afford
insufficient_funds_method string paysafecard or blik. When the wallet cannot cover the price, pay the remainder via this provider instead of failing: Steam applies the wallet first and a payment link for the exact remainder appears under wallet payments (see the billing endpoint), with purpose purchase. The purchase completes once that link is paid. Omit for wallet-only behaviour, where an unaffordable price fails the task. Refused with the random modes, which are wallet-only

With an insufficient_funds_method set, accounts that already have a pending wallet payment are skipped (topup_already_pending): Steam allows one open checkout transaction per account, and a new one would silently cancel the pending payment. With blik, accounts whose wallet currency is not PLN are skipped (unsupported_wallet_currency), and accounts with no Steam wallet at all are skipped as no_wallet_currency.

Random modes. random_store draws from Steam's own store search: games only (no DLC, soundtracks, demos, bundles or tools), priced above zero and at or below max_price, in the account's store region. Every account draws independently, so a fleet does not end up with the same game everywhere. random_list takes your package_list instead and gives each account one entry it does not own, in pick_order. In both modes the worker verifies ownership and the regional price live on the account before checkout, confirms the checkout total is still within the cap, and pays from the wallet only. The random modes skip accounts with no Steam wallet at creation (no_wallet_currency).

A random-mode task completes with an outcome in its task_data. purchased carries app_id, package_id, name, price_cents, currency, formatted_total, transid and new_wallet_balance, and the game is recorded in the account's library right away. skipped carries a skip_reason: no_wallet, insufficient_funds (the balance is below every matching game), no_matching_games (nothing unowned within the cap and filters), all_owned, over_max_price, not_purchasable (nothing on the list is sold in the account's region), price_changed (the checkout total exceeded the cap, so the transaction was cancelled), ownership_unknown (the account's licences could not be read, so nothing was bought) or currency_mismatch. Both outcomes also report candidates_checked, max_price_cents and wallet_balance_cents. A skipped account is a completed task, not a failed one: nothing was charged and nothing went wrong.

buy_item_store_items buys an item from a game's Steam Item Store with the account's wallet balance.

Field Type Description
item_url string The item's store page, https://store.steampowered.com/itemstore/{app_id}/detail/{item_id}/. Max 255 characters
quantity integer How many to buy on each account. 1 to 100, default 1

Steam only sells item store items to accounts that own the game. A free-to-play game is added to the account first; for a paid game the task fails until the account owns it. The account pays in its own wallet currency, and a wallet that cannot cover the order fails the task before any checkout starts. On success the task's task_data gains order_id, item_name, formatted_total, total_cents, currency and new_wallet_balance.

assign_currency gives a walletless account a wallet currency, so market and checkout work can run.

Field Type Description
currency string A wallet currency code, USD by default
country string EUR only: the store country to open every selected account's wallet in. One of AT, BE, CY, DE, EE, ES, FI, FR, GR, HR, IE, IT, LT, LU, LV, MT, NL, PT, SI, SK, AD, MC, SM, VA, ME, XK, SE. Rejected with any other currency

Only accounts with no wallet currency are eligible: Steam refuses to open a second wallet. A billing address for the currency's country is generated and stored on each account as part of the task. Opening the wallet also fixes the account's store country to that address's country for good, and later top-ups must match it. Every currency but EUR implies its country. For EUR without a country, each account keeps its own store or login country when that country has a EUR store; accounts from any other country are skipped as no_eur_country rather than moved to a different store.

buy_point_shop_items spends Steam Points on exact rewards or a filtered catalogue shortlist. Set sort to random to buy from that list or shortlist in shuffled order.

Field Type Description
mode string exact (default) or automatic
definition_ids array 1 to 100 Points Shop definition ids. Required in exact mode
classes array Reward category ids, including 1 (seasonal badges). Leave empty to include all supported categories in automatic mode
game_mode string any (default) or specific
app_ids array Steam app ids, 1 to 100. Required when game_mode is specific
minimum_item_cost integer Lowest item cost to include, at least 0
maximum_item_cost integer Highest item cost to include, at least the minimum
sort string cost_low (default), cost_high, name, newest, or random. In exact mode, every value except random follows the selected list order. In automatic mode, random draws a random shortlist instead of cheapest-first
category_strategy string catalogue_order (default) or one_each_first. Only affects automatic mode
max_purchases_per_category integer Optional successful-purchase limit for each matching reward category, 1 to 100. Only affects automatic mode
owned_games_only boolean Build the matching-item shortlist from games the selected accounts own, and skip rewards for games an account does not own. Steam event rewards are always kept. Default true. Only affects automatic mode
budget_mode string fixed_points (default), balance_percentage, or available_balance
budget_value integer Percentage of the live balance to spend, 1 to 100. Required for balance_percentage, default 50
max_points integer Fixed budget or absolute cap, 1 to 10,000,000. Default 1000
minimum_remaining_points integer Points that must remain after buying, 0 to 10,000,000. Default 0
max_purchases integer Successful purchases allowed per account, 1 to 100. Default 10
max_attempts integer Catalogue candidates tried per account, 1 to 100 and at least max_purchases. Default 50
vary_by_account boolean Give each account a stable rotated candidate order. Default true
equip_after_purchase boolean Equip each purchased cosmetic on the profile. Default false

The server freezes the matching definition ids and approved prices when you create the task. Every account uses that same immutable shortlist. With sort set to random, the frozen shortlist is a random sample of the matching catalogue (or the picked list in exact mode), and the worker shuffles that list before buying. The shuffle is stable for a given plan and account, so a retry does not pick a new order. vary_by_account (default true) gives each account its own shuffle. The worker fetches each live definition again before buying and skips it when the price changed, the item disappeared, the account already owns it, or Steam says the account is not eligible. Set max_purchases to 1 to buy one random item per account.

Seasonal badges (community_item_class 1) use Steam's badge-level redeem, not the ordinary item redeem. One purchase can buy several levels of the same badge, one level per 1000 points on the current Summer and Winter collections, up to level 40. max_purchases counts each level. An account already at the cap is recorded as already owned. Only the collection Steam's Seasonal Badge page is selling right now (Summer from June through November, Winter from December through May) can be selected or redeemed. Older collections stay in Steam's catalogue as active, and the worker records them as unavailable instead of spending points.

When owned_games_only is on (the default) and at least one selected account has a synced library, the frozen shortlist is taken from those owned games, plus rewards that are not tied to a game, plus rewards from Steam's own events (sale and anniversary items, including seasonal badges). Event rewards carry the event's app id, which no library owns, but Steam sells them to every account. That stops cheapest-first from filling the list with rewards a CS2 farm cannot buy. Each account still drops anything it does not own at run time. Rewards that cost nothing, rewards not tied to a game, and event rewards stay eligible for that per-account filter, and an account whose library has never been refreshed is not filtered. Owning a game is not always enough: for free-to-play games such as Counter-Strike 2, Dota 2, and Team Fortress 2, Steam also refuses rewards until the account has enough playtime, which a library sync cannot see. Expired free promotions (no end date) are left out of matching-item plans. Currently running free giveaways stay in.

A run that buys nothing because every remaining candidate was unavailable, not eligible, or for a game the account does not own fails the task instead of completing. An account that only skipped items it could not afford, or already owned, still completes.

Three kinds of candidate are skipped without becoming an attempt, so they never consume one and stay available to a later run. owned_games_only drops rewards belonging to a game the account has no licence for, because Steam always refuses those. A candidate whose frozen price is above what the account can spend is skipped without a lookup, so an account short on points finishes immediately instead of working through the whole shortlist. And once Steam refuses an account for a game (no licence, or not enough playtime), the account skips that game's other candidates for the rest of the run, so its attempts go to items it can buy. The counts are reported on the task as point_shop_purchase.skipped.not_owned, point_shop_purchase.skipped.unaffordable, and point_shop_purchase.skipped.not_eligible.

Set category_strategy to one_each_first to prioritize one successful purchase from every matching reward category before buying another item from a category that is already covered. Failed ownership or eligibility checks move to another candidate in the missing category. A category is not guaranteed to complete when Steam rejects every matching candidate, the task reaches max_attempts, or the account runs out of spendable points.

When vary_by_account is also enabled, each account gets a stable variation of the category and item order while keeping the one-per-category priority.

Set max_purchases_per_category to stop one category from taking more than its share of the budget. For example, combine one_each_first with a per-category limit of 1 to buy at most one profile background, one avatar frame, and one item from every other matching category.

minimum_remaining_points is applied after the budget mode. For example, an account with 4,000 points, a 50% budget, and a 2,500 point reserve can spend at most 1,500 points. max_points is always a hard cap, including percentage and available-balance modes.

Set equip_after_purchase to put what was bought straight onto the profile. Four reward categories are profile slots: profile backgrounds, mini-profile backgrounds, avatar frames and animated avatars. A purchased seasonal badge is featured on the profile instead. Emoticons, stickers and chat effects are consumed per use, and startup movies are a Steam Deck setting, so none of those are ever equipped. A profile holds one item per slot, so when a run buys several for the same slot the most expensive one is equipped. Equipping happens after every purchase is recorded, and a failure to equip is reported in the task result without failing the task, because the points are already spent. The result gains an equipped array of {slot, community_item_id, equipped, error_message} (slot is a profile slot or favorite_badge), and the account's profile_items.equipped is refreshed by the next details refresh.

Profile

set_profile writes the profile, typed in once or drawn per account from your profile library.

Field Type Description
mode string manual (default), profile, or group
persona_name string 2 to 32 characters. Required when mode is manual
custom_url string Steam vanity slug (steamcommunity.com/id/{slug}). Letters, numbers and underscores, 3 to 32 characters. Omit or null to leave the current custom URL alone. Library profile / group modes send this only on a profile's first-ever apply
avatar string Path of an already-stored avatar on the public disk. There is no binary upload on this endpoint
summary string Profile description, up to 8,000 characters
country string Two-letter country code
profile_id uuid A library profile. Required when mode is profile
profile_group_id uuid A profile group. Required when mode is group
allow_reuse boolean Default false. With reuse off, the batch is capped at the number of unused profiles in the pool

A custom URL can belong to only one account on all of Steam. When the slug is taken (or Steam refuses its format), the task tries two numbered variants of it. If none is free, the account keeps its current custom URL, the rest of the profile still applies, and the task completes rather than failing.

The result is {persona_name, custom_url, requested_custom_url, custom_url_outcome, custom_url_refusal, custom_url_steam_message, summary, country, avatar_url, profile_id}. custom_url is the URL the account has now. custom_url_outcome is applied (the requested slug), variant (a numbered variant of it), kept (the account kept its current URL), or null when the task did not ask for a custom URL. When it is variant or kept, custom_url_refusal says why the requested slug did not stick: taken, invalid, refused (Steam gave no reason, which it does after several profile changes on one account in a short time), or rate_limited, and custom_url_steam_message carries Steam's own words.

Steam limits how often one account's profile can change. After several saves on the same account in a short time it refuses further changes for a few hours, and the task fails with an error saying so.

change_profile_privacy flips Steam visibility settings. Every field is optional and an omitted field is left unchanged, so one call can change a single setting across the fleet.

Field Type Description
profile integer 1 private, 2 friends only, 3 public
owned_games integer 1, 2, 3
inventory integer 1, 2, 3
friends_list integer 1, 2, 3
inventory_gifts integer 1 private or 3 public. No friends-only step
playtime integer 1 private or 3 public
comment_permission integer 0 friends, 1 public, 2 private

Send at least one. A config with none would read every account's settings and write them back unchanged, so it is refused with an error on config.profile.

equip_profile_items puts cosmetics an account already owns onto its profile.

Field Type Description
slots array Any of profile_background, mini_profile_background, avatar_frame, animated_avatar. Defaults to all four
strategy string newest (default), random, or name
name_contains string Up to 120 characters. Required when strategy is name
skip_if_equipped boolean Leave slots that already hold something. Default true

There is no field for a specific item, and that is deliberate: every account owns a different set, so the item is resolved per account when the task runs. newest takes the most recently acquired, random picks one, and name keeps only items whose name contains your text, with the newest of those winning a tie.

A profile holds one item per slot. A slot the account owns nothing for is reported and skipped, never treated as a failure, and with skip_if_equipped on a repeat run only fills the gaps. One slot Steam refuses does not stop the others; the task only fails when every slot it tried was refused.

The result carries a slots array of {slot, outcome, community_item_id, name}, where outcome is equipped, already_equipped, no_match, owns_nothing, or failed. After anything is equipped the account's profile_items.equipped is re-read from Steam and updated, so it reflects the change without waiting for a details refresh.

set_profile_theme sets the colour theme on each Steam profile. This is the chip row on Steam's Profile settings (Default, Summer, Midnight, Steel, Cosmic, Dark mode), not a Points Shop item and not part of set_profile. Steam writes it through IPlayerService/SetProfileTheme, a different surface from the community profileSave that name, summary, country and custom URL use.

Field Type Description
mode string selected (default) or random
theme string Required when mode is selected. One of default, Summer, Midnight, Steel, Cosmic, DarkMode
themes array Required when mode is random. One or more of those same ids

selected applies the same colour to every account. random picks one colour per account from themes when the task is created, so a retry reapplies the same choice. default is Steam's unstyled profile (an empty theme_id on the wire).

The result is {mode, theme, themes}, where theme is the colour that was written.

set_favorite_badge features one of the badges an account has earned on its profile.

Field Type Description
strategy string recommended (default), highest_level, or random
skip_if_set boolean Leave accounts that already feature a badge. Default true

As with equipping, there is no field for a specific badge: each account has earned a different set, so it is ranked per account at run time. recommended takes the years-of-service badge first, then the highest seasonal sale badge, then the owned-games badge, and features nothing if the account has none of those. highest_level ignores the type and takes the highest level, then the highest XP. random picks uniformly among the account's Steam-wide badges, so a fleet run does not clone one badge onto every profile.

Only Steam-wide badges are eligible. Per-game badges are addressed by a different identifier that SteamLabs does not write, so they are never featured.

The result is {strategy, outcome, badge_id, badge_name, level, reason}, where outcome is set, already_set, no_badges, or no_match, and reason says which rule picked the badge: years_of_service, seasonal_sale, owned_games, highest_level, or random. badge_name may be null when Steam's badge page could not be read; the ranking still works, though the seasonal-sale step is skipped, since that is the one step that needs names.

claim_community_badge is a different job from featuring a badge or redeeming a paid Points Shop sale badge. Steam awards Community Pillar (and later levels of the same community badge) when enough quests on /badges/2 are done. There is no one-click claim RPC. The worker reads Steam's official quest-progress list (and the badge page for the title and the "N of 28" counts) and runs the remaining quests that have a stable web endpoint and spend nothing: Discovery Queue, discussions search, wishlist, workshop rate or subscribe (then unsubscribe), view a broadcast, join the Steam Trading Cards group, post then delete a status, comment then delete on the account's own profile, activity-feed upvote, set a real name when it is empty, and feature a badge if none is featured. Viewing a guide only counts from the in-game overlay, so that quest is skipped.

It skips anything that costs wallet or points, needs another account, writes a public review, or is account-security / market work (trade, market, phone, Steam Guard, 2FA, craft cards, screenshots, videos, add a friend, play a game, chat emoticon, avatar upload, profile background). Limited accounts can finish fewer quests. Steam still awards the next badge level when the remaining count is high enough.

The result is {outcome, badge_id, badge_name, level_before, level_after, completed_before, completed_after, total, quests}, where outcome is completed, already_complete, or partial, and each quest is {id, outcome, detail?}. A repeat run is cheap: finished quests are reported as already_done and not retried.

craft_game_badges crafts per-game trading-card badges. For every game where the account holds a complete set of cards, it crafts one badge level at a time until the cards run out or the badge reaches level 5. It only ever uses cards the account already has; it never buys cards or spends wallet or points.

Field Type Description
include_foil boolean Also craft foil badges. A foil set is a separate single-level badge per game. Default false
max_crafts integer Stop after this many badge levels on the account, across all its games, 1 to 100. Omit for no cap

There is no field for a game: the worker reads the account's own badge listing on Steam, so one task fits every account in a selection. Steam awards 100 XP per game-badge level and drops its usual items (an emoticon, a background, sometimes a booster pack) with each craft; they land in the inventory and show up on the next inventory refresh. Crafting consumes one card of each kind per level.

The result is {include_foil, max_crafts, outcome, games_checked, crafts, xp_gained, crafted, skipped}, where outcome is crafted, partial (some crafts failed), or nothing_to_craft. Each entry in crafted is {app_id, game, badge_name, foil, level_before, level_after, crafts, xp_gained, dropped_items, steam_level} (steam_level is the Steam level the account reached when the XP levelled it up, else null) and each entry in skipped is {app_id, game, foil, reason, cards_owned?, cards_total?, detail?} with reason one of incomplete_set, max_level, max_crafts, craft_failed, or unreadable. A run where every craft Steam was asked for failed is reported as a failed task.

join_steam_groups joins Steam community groups. Name a group by its link, the name in its link (tradingcards for steamcommunity.com/groups/tradingcards), or its 64-bit group ID.

Field Type Description
mode string list (default), random_list, or random_steam
groups array Up to 200 group links, names or IDs. Required unless mode is random_steam
count integer 1 to 25, default 3. How many new groups each account joins in the random modes. In random_list it cannot be more than the groups you listed
min_members integer Default 1000. In random_steam, only groups with at least this many members are picked

list joins every group on the list. random_list joins count random groups from it, drawn per account when the task runs, so accounts end up in different groups and a group the account is already in is never one of the picks. random_steam finds public groups through Steam's own group search instead, and never picks a group whose name points at adult content, cheats or skin giveaways.

Groups that need approval (Steam's Request To Join button) or an invite are skipped and never requested. The worker reads each group page before posting, because Steam files a join request through the same form. A join only counts once the group shows up in the account's own group list.

The result is {mode, results, requested}, where each entry in results is {group, name, outcome, detail?} and outcome is joined, already_member, needs_approval, invite_only, not_found, or failed. requested is the count a random mode asked for, so a run that found fewer joinable groups shows the gap.

leave_steam_groups is the undo.

Field Type Description
mode string listed (default) or all
groups array Up to 200 group links, names or IDs. Required when mode is listed

all leaves every group the account is in, including private and invite-only groups it cannot rejoin on its own. The result is {mode, results}, where outcome is left, not_member (a listed group the account is not in), or failed.

update_wishlist rotates the account's Steam wishlist so it keeps looking like a real person's: a few new games go on, a few old ones come off. Every field is optional. A bare task adds 1 to 3 popular paid games and removes 1.

Field Type Description
remove_count integer 0 to 100, default 1. How many of the games already on the wishlist to remove, picked at random
remove_all boolean Default false. Remove every game that is on the wishlist when the run starts (up to 100 per run) instead of remove_count
add_min integer 0 to 20, default 1. The fewest games each account adds
add_max integer 0 to 20, default 3. The most games each account adds. Each account draws its own number in the range. Set both to 0 to only remove
source string store (default) picks popular games from Steam's store search. list picks from games
include_free boolean store only. Default false, so only paid games are added
min_reviews integer store only. Only games with at least this many Steam user reviews, 0 to 10,000,000. Default 1000. Send null for no minimum
min_review_score integer store only. Only games whose positive review share is at least this percentage, 0 to 100. Omit for any
games array Required when source is list. Up to 200 entries, each a store link (https://store.steampowered.com/app/413150/…) or an app ID ("413150" or 413150)

The task must remove or add something: add_max of 0 with remove_count of 0 and no remove_all is refused, and so is add_min above add_max.

How a run works, per account:

  1. It reads the wishlist and the account's owned and ignored games from the Steam store. If the store will not answer for the account, the task fails and nothing changes.
  2. It adds games first. Games the account owns, already wishlisted or has ignored are never picked, and every account draws its own, so a fleet does not end up with identical wishlists. In store mode the picks come from different parts of Steam's popularity order. Steam may still refuse a game (for example one not sold in the account's region); the worker then tries the next one.
  3. It then removes games, only from the ones that were on the wishlist before this run. Removal by remove_count never empties the wishlist: the last game stays. Because adds come first, a one-game wishlist still rotates. Only remove_all can leave it empty, and only when nothing was added.

Each change waits a random 1.5 to 4 seconds after the previous one.

The result is {outcome, wishlist_before, wishlist_after, add_target, added, removed, skipped}. outcome is updated, partial (some changes failed) or unchanged. added and removed list {app_id, name}. Each entry in skipped has a reason: wishlist_empty, kept_last_game, removal_cap (with remaining), no_candidates (ran out of games to add, with wanted and found), add_refused or remove_refused (Steam said no to that game), or add_failed or remove_failed (the request failed, with detail). The per-game reasons also carry app_id and name. A run where Steam accepted none of the changes it tried is reported as a failed task.

Inventory and storage

store_items deposits matching CS2 items into the account's storage units.

Field Type Description
store_mode string all (default) stores everything matching, keep leaves some in the inventory
keep_count integer How many matching items stay out. Required when store_mode is keep
unit_mode string auto (default) picks units with space, named targets units by name
unit_name string Up to 20 characters. Required when unit_mode is named
max_items integer Cap on items moved per account
item conditions The shared block above, without games

withdraw_items moves matching items back out of storage.

Field Type Description
unit_mode string all (default) or named
unit_name string Up to 20 characters. Required when unit_mode is named
max_items integer Cap on items moved per account
item conditions The shared block above, without games

rename_storage_unit renames storage units across accounts.

Field Type Description
unit_mode string all (default) or named
unit_name string Which units to rename. Required when unit_mode is named
new_name string The new name, up to 20 characters

distribute_items sends items to one destination, or spreads them with routing rules.

Field Type Description
mode string simple (default) or advanced
destination_type string own (default) or external. Required when mode is simple
destination_account_id uuid The receiving account. Required when destination_type is own
trade_url string A full https://steamcommunity.com/tradeoffer/new/?partner=…&token=… URL, up to 500 characters. Required when destination_type is external
rules array Routing rules, at least one. Required when mode is advanced
auto_accept boolean Accept the offer on the receiving side. Default true
message string Trade offer message, up to 128 characters
item conditions The shared block above, with games

Each entry of rules is an object:

Field Type Description
action string route (default) sends the matching items, skip leaves them alone
strategy string round_robin (default), random, fill_value, top_up_value
target_account_ids array Receiving accounts you own
target_tag_ids array Account tags. Every account carrying any of them receives
target_group_ids array Account groups. Current members receive
target_trade_urls array Receiving trade URLs, up to 500 characters each
exclude_tag_ids array Account tags. An account carrying any of them never receives, whatever includes it
exclude_group_ids array Account groups. An account in any of them never receives, whatever includes it
target_value number Dollars fill_value sends per target this run (a ceiling), or the inventory total top_up_value brings each target up to (a floor, so the last item can land a target slightly over)
items_limit_per_target integer Cap on items per target
item conditions The shared block above, with games, scoped to this rule

Market

sell_items lists matching marketable items, priced live per item.

Field Type Description
marketplace string steam (default), csfloat, marketcsgo, skinland, dmarket, skinport, assetpay. Decides which pipeline runs and which plan entitlement is checked
mode string undercut (default), reference_percent, buy_order, top_bid, manual. Required on every listing venue. Omit it on skinland. On assetpay the modes are instant (default, sold outright at the Sell now price), cheapest (listed one cent under the store price, never below the Sell now price), instant_markup (listed on AssetPay's store at percent of the Sell now price), market_percent (listed at percent of another market's price, see reference_source) and manual. The store price is what the item costs on AssetPay's store right now, not counting SteamLabs listings, so SteamLabs sellers tie instead of undercutting each other. The Sell now price is what AssetPay pays you for it on the spot
undercut_cents number How far under the lowest listing to price. Default 1. At least 1 on every venue except market.csgo, which prices in tenths of a cent and accepts one decimal place down to 0.1
percent number Percentage of the reference price, 1 to 500. Required when mode is reference_percent. Default 100. On assetpay with instant_markup it is the percentage of the Sell now price to list at, 100 or more, and required
reference_source string assetpay with market_percent only. csfloat (default), steam_market or marketcsgo. The listing never goes below the Sell now price. Items with no price from that market are skipped
follow_cheapest boolean assetpay with cheapest only. Keep each listing one cent under the store price as it moves. Default true
instant_sell_after_days integer assetpay listing modes only. Days after which an unsold listing sells to AssetPay at the Sell now price, 1 to 365. Omit to keep listings up until they sell
floor_percent number Refuse to list below this percentage of the reference price, 1 to 100. Default 70. Ignored when mode is manual
manual_price_cents integer The exact buyer-pays price in cents, at least 3. Enough for a single named item when mode is manual
manual_prices_cents object Exact prices keyed by catalog item id. Required when mode is manual and item_ids names more than one item. Every named item needs its own cents value
manual_currency string The wallet currency the exact prices are quoted in, for example PLN. Default USD
manual_convert boolean Convert the prices into each account's own wallet currency instead of requiring a match. Default false
max_items integer Cap on items listed per account
float_min number Lowest CS2 paint wear to include, 0 to 1. Items without a float are skipped
float_max number Highest CS2 paint wear to include, 0 to 1
confirm_sell_all boolean Acknowledges an unbounded sale. Default false
auto_reprice boolean Enroll successfully created CSFloat, market.csgo, or DMarket listings into automatic repricing. undercut mode only. Default false
auto_reprice_interval_minutes integer Automatic check cadence: 15, 30, 60, or 240. Default 30
item conditions The shared block above, with games

Rules the sell form enforces visually apply here too:

  • buy_order pricing is only available on steam, and top_bid only on marketcsgo and dmarket. The wrong pairing fails on config.mode.
  • skinland buys at its own quote, so it takes no mode. Sending one fails on config.mode. Leaving it off is how you sell there.
  • A config with no item condition, no max_items and no confirm_sell_all would list every marketable item on every selected account. It is refused on config.confirm_sell_all until you set that flag to true.
  • manual needs named catalog items, so config.item_ids must name at least one. An empty list is refused on config.item_ids.
  • A mixed item_ids list needs manual_prices_cents with a cents value for every named item. One shared manual_price_cents is refused on config.manual_prices_cents.
  • manual_convert is Steam-only. csfloat and marketcsgo quote in USD and have no per-account wallet to convert into, so pairing them fails on config.manual_convert.
  • auto_reprice supports CSFloat, market.csgo and DMarket and requires mode: "undercut" plus a floor_percent. Each successful listing captures its own absolute USD floor. Listings whose venue reference price is missing are still created but are not enrolled.

Exact pricing across accounts

mode: "manual" lists at prices you type rather than prices derived from the order book. Name the items under item_ids. One item can use manual_price_cents. Several items need manual_prices_cents keyed by those same ids. Copies of one item across accounts share that item's price. Two things then decide how Steam spends the numbers:

manual_currency says what the number means. Steam wallets each have their own currency, so 707 is 7.07 zł on a PLN wallet and $7.07 on a USD one. Accounts whose live Steam wallet is not in manual_currency are skipped, and their items come back under the task's skipped list with reason wallet_currency_mismatch.

manual_convert: true trades exactness for coverage: nothing is skipped, and each account lists at that value converted into its own wallet currency using the item's own order book as the rate. Items whose rate cannot be derived are skipped with no_fx_reference.

Bash
curl -X POST "https://dashboard.steamlabs.dev/api/v1/tasks" \
  -H "Authorization: Bearer $STEAMLABS_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{
        "type": "sell_items",
        "config": {
          "marketplace": "steam",
          "mode": "manual",
          "manual_price_cents": 707,
          "manual_currency": "PLN",
          "manual_convert": false,
          "item_ids": ["<item-id>"]
        },
        "filters": { "market": "eligible", "inventory": "synced" }
      }'

place_buy_order places the same buy order across accounts.

Field Type Description
game string cs2 (default), tf2, steam
market_hash_name string The exact item name, up to 255 characters
pricing_mode string fixed (default) or outbid
price number Unit price, at least 0.03, in price_currency. Required when pricing_mode is fixed
max_price number Ceiling when outbidding, at least 0.03, in price_currency. Required when pricing_mode is outbid
price_currency string What price and max_price are in. A currency code (USD, EUR, IDR, ...) is converted into each account's wallet currency, or wallet uses the number as-is in every wallet. Omitted means wallet, so existing clients keep working. The panel defaults to USD
quantity_mode string fixed (default) or max_affordable
quantity integer 1 to 1,000, default 1. Required in fixed mode, refused in max_affordable mode
max_quantity integer Optional cap for max_affordable, 1 to 1,000. Leave it out for no cap beyond Steam's 1,000 per order. Refused in fixed mode

Only accounts that can actually place buy orders are eligible: market eligible, mobile authenticator, billing address on file, and enough wallet balance to fund price x quantity on top of their already-open orders (Steam caps open orders at ten times the balance). Everything else is reported as accounts_skipped, split by reason in accounts_skipped_reasons (insufficient_funds, cannot_place_buy_orders, task_in_flight). In outbid mode the price is resolved per wallet currency at creation time.

Steam prices a buy order in the account's own wallet currency and converts nothing, so one bare number means a different amount on every wallet: 1250 is about $0.08 in IDR and about $30 in UAH. Send price_currency whenever your accounts hold more than one currency. With a currency code, the price is converted into each wallet's currency at creation time at the latest daily exchange rate, rounded down, so a converted cap never grows. Amounts are in Steam's x100 minor units for every currency, including ones shown without decimals such as IDR and JPY.

Some accounts get a failed child task instead of an order, with the reason in error. Nothing is bid on them, and retrying the batch's failures prices them again:

  • In outbid mode, the account's live order book could not be read (Steam did not answer, or the account has no known wallet currency). The cap is never bid blind.
  • price_currency names a currency, and no exchange rate to the account's wallet currency is available.
  • The final bid is more than three times the item's Steam market price converted into the wallet's currency. This check runs on every place_buy_order, including the ones the market endpoint creates, and only when SteamLabs has a market price and an exchange rate for the item.

max_affordable buys as many units as each wallet can actually pay for. When the task runs, the worker reads the account's live wallet balance, subtracts what its other open buy orders still hold, divides by the price it is bidding and rounds down, capped at max_quantity. Steam's ten-times allowance is not used, so every unit is paid for if the whole order fills. Accounts whose last synced balance cannot cover one unit are skipped as insufficient_funds. In outbid mode that check only skips empty wallets, because the real bid is set per currency later and can be lower than max_price.

A finished place_buy_order task reports an outcome in its task_data:

  • placed carries buy_order_id, quantity (the units actually bid on), buyer_pays_per_unit_cents, total_cents, currency, purchased and quantity_remaining. In max_affordable mode it also reports wallet_balance_cents, committed_cents (held by other open orders), available_cents and quantity_limited_by (wallet, max_quantity or steam_limit).
  • skipped (only in max_affordable mode) carries a skip_reason: insufficient_funds (the live wallet cannot pay for one unit) or currency_mismatch (the wallet is no longer in the currency the price was set for). It reports the same balance fields and resolved_quantity: 0. A skipped account is a completed task, not a failed one: nothing was bid and nothing went wrong.

buy_market_listing buys one specific Steam market listing, chosen by its listing id, on one account. It targets exactly one account: send a single account_id, or a wider selection is refused on account_ids. For several purchases in one request, or to set the cap in cents, use POST /api/v1/market/steam/listings/buy (see Market).

Field Type Description
game string cs2 (default), tf2, steam
market_hash_name string The exact name of the item the listing belongs to, up to 255 characters. The listing's price is read from its item page, so this has to match
listing_id string The Steam listing id, a number of up to 20 digits
max_total number The most the account may pay, fees included, in its own wallet currency, with no conversion. At least 0.03

Only an account that can buy is eligible: market eligible, mobile authenticator, billing address on file, and a last synced wallet balance that covers max_total. Otherwise accounts_skipped_reasons says why (insufficient_funds, cannot_place_buy_orders, no_wallet_currency, task_in_flight).

A finished task reports an outcome in its task_data: purchased (with total_cents, wallet_balance_after_cents and the asset_id the item got in the inventory), or one of sold_out, price_changed, insufficient_funds, not_found and currency_mismatch, each a completed task with a readable message rather than a failure. The Market page documents every field.

cancel_buy_orders and cancel_market_listings cancel the account's open buy orders, or its active and awaiting-confirmation Steam listings. By default they cancel all of them. In matching mode they cancel only what matches every condition you send.

Field Type Description
mode string all (default) or matching. Left out, it follows the conditions: send any condition and the task narrows
market_hash_names string[] Exact item names, up to 100. The StatTrak™ or Souvenir version of an item is a different name
price_min number Lowest unit price to include, in each account's own wallet currency, with no conversion. For a listing this is what the buyer pays
price_max number Highest unit price to include. At least price_min
older_than_days integer Only orders or listings at least this many days old, 1 to 3650
JSON
{
    "type": "cancel_buy_orders",
    "filters": { "tags": ["019fb432-6d10-70bb-a1c9-4e2f8b7d5a01"] },
    "config": {
        "mode": "matching",
        "market_hash_names": ["AK-47 | Redline (Field-Tested)"]
    }
}

A matching task is resolved when you create it, against the orders and listings SteamLabs last saw on each account. Each account's task carries the ids it will cancel, and an account with nothing matching gets no task and no login. It is reported as nothing_to_cancel in accounts_skipped_reasons. That is what makes retiring one position across a fleet cost one login per account that holds it.

mode: "all" together with conditions is refused with 422, and so is mode: "matching" with none: neither has a reading that is safe to guess.

Steam does not date buy orders, so a buy order's age counts from when SteamLabs first saw it. For an order placed through SteamLabs that is when you placed it. A listing's age is its listing time on the market.

withdraw_csfloat_balance cashes each account's CSFloat balance out to one of that account's own payout methods. You pick the kind of payout method, and each account uses its own method of that kind. Payout methods are added in the account's Stripe dashboard on CSFloat (csfloat.com/profile/withdraw), once per account, and that is the only place they can be added. So a withdrawal can only ever pay the account's own bank, card or wallet.

Field Type Description
destination string crypto (default), bank or card
amount_mode string all (default) withdraws everything withdrawable, fixed withdraws exactly amount, keep withdraws everything above keep_amount
amount number USD, 5 to 25,000. Required when amount_mode is fixed
keep_amount number USD left on CSFloat, 0 to 25,000. Required when amount_mode is keep
speed string standard (default) or instant. Instant costs an extra fee and only works on payout methods that offer it
JSON
{
    "type": "withdraw_csfloat_balance",
    "filters": { "tags": ["019fb432-6d10-70bb-a1c9-4e2f8b7d5a01"] },
    "config": {
        "destination": "crypto",
        "amount_mode": "keep",
        "keep_amount": 10
    }
}

Accounts without a working CSFloat connection (an API key and a dedicated proxy) are skipped as not_connected_csfloat. Accounts whose last synced CSFloat balance cannot cover the config are skipped as insufficient_csfloat_balance. The task still checks the live balance before it withdraws.

Each task works out the amount from the live withdrawable balance, capped at CSFloat's $25,000 per withdrawal. In all and keep mode an amount under the payout method's minimum (usually $5) is not an error: the task finishes with task_data.withdrawn: false and a skipped_reason of below_minimum or nothing_withdrawable. These fail the task with a reason:

  • the account has not finished CSFloat's payout setup
  • it has no payout method of the chosen kind
  • the method does not support the chosen speed
  • a fixed amount is more than the account can withdraw

A finished withdrawal reports:

Field Type Description
withdrawn boolean true when money was sent
amount_cents integer USD cents taken from the CSFloat balance
fee_cents integer | null CSFloat's withdrawal fee, in USD cents
net_cents integer | null amount_cents minus fee_cents
destination object type (blockchain_address, bank_account or card), currency and a label such as the bank name and last four digits or the crypto network
transaction_id string | null The withdrawal's id in the account's CSFloat transaction history

A withdrawal is never sent twice for the same task, not even after a lost connection. If CSFloat stops answering mid-request, the task looks the withdrawal up in the account's transaction history instead of sending it again. When it is not there, the task fails and asks you to check CSFloat before retrying.

CS2 armory and drops

buy_store_items buys from the in-game CS2 store with the account's wallet balance.

Field Type Description
item string armory_pass (default), storage_unit, name_tag
quantity integer At least 1, default 1. Capped per item: 5 armory passes, 10 storage units, 10 name tags
activate_after_purchase boolean Activate armory passes as soon as they are bought. Default true

buy_armory_items spends armory credits, split by weight.

Field Type Description
items_to_buy array At least one allocation line
total_balance_percentage integer How much of the credit balance to spend, 1 to 100. Default 100

Each line is { "item": "…", "percentage": 25 }, where item is one of fever_case, arabesque_collection, spy_tech_collection, overpass_2024_collection, auto_racing_stickers, fruits_and_veggies_stickers, community_2025_stickers, missing_link_charms, missing_link_community_charms, dr_boom_charms, small_arms_charms, and percentage is 1 to 100.

activate_armory_passes activates bought-but-inactive passes.

Field Type Description
max_to_activate integer 1 to 1,000. Leave it out to activate every inactive pass

claim_weekly_drops claims the account's two weekly CS2 drops.

Field Type Description
strategy string highest_price (default) picks for you, manual claims what you name
ignore_graffitis boolean Skip graffiti when picking. Default false
claim_item_ids array At most 2 ids, strings up to 64 characters. Steam allows two claims a week

Run fetch_weekly_drops first if you want to review the options before claiming.

trade_up_contract crafts 10 same-rarity skins into 1 of the next rarity.

Field Type Description
trade_up_mode string clear_space (default) burns cheap inventory, best_value chases expected value
trade_up_max_item_price number Most expensive input to consume, at least 0.01. Required when trade_up_mode is clear_space. Default 0.10
trade_up_max_contracts integer Contracts per account, 1 to 100. Default 25
trade_up_min_ev number Minimum expected value, -100 to 1000. Default 8
trade_up_max_input_cost number Ceiling on the combined input cost of one contract
trade_up_rarities array Input rarities, at least one of 1 consumer, 2 industrial, 3 mil-spec, 4 restricted, 5 classified. Default [1, 2]
trade_up_stattrak string any (default), normal, stattrak
trade_up_dry_run boolean Evaluate the guardrails and report a digest without crafting. Default false

Planning has side effects: a real run reserves the input items and writes ledger rows per account. Accounts with no viable contract under your guardrails are skipped. Set trade_up_dry_run to true first, which answers 200 with the digest and consumes nothing.

Each account's task crafts its contracts one by one and reports every contract in task_data.contract_results (contract_id, status of completed, failed or reconciled, the output fields on a completed one, error on a failed one), next to contracts_total, contracts_completed, contracts_failed and contracts_reconciled. A contract is reconciled when its input skins were already gone, so nothing was crafted for it. GET /api/v1/tasks/{id} also returns the same result digested under trade_up.

A task that crafted at least one contract finishes Completed. One that crafted nothing finishes Failed with an error that starts with No trade-up contract could be crafted: and names the most common reason, and it still carries contract_results. If Steam stops answering craft requests three times in a row, the task stops there and records the remaining contracts as failed with not_attempted: true. A failed or skipped contract's input skins are released for the next run, and POST /api/v1/tasks/{id}/retry re-runs the same contracts.

If you delete a trade-up task before it finishes, its unfinished contracts are marked cancelled within about 25 minutes and their input skins become available again.

TF2

buy_tf2_store_items buys from the in-game TF2 store with the account's wallet balance.

Field Type Description
item string backpack_expander (default)
quantity integer At least 1, default 1, capped at 10
use_after_purchase boolean Apply the item as soon as it is bought. Default true

use_tf2_items applies unused items already sitting in the backpack.

Field Type Description
item string backpack_expander (default)
max integer At least 1. Leave it out to use every matching item

Delete many tasks

DELETE/api/v1/tasks

Delete a list of tasks. Unfinished ones are cancelled first.

API key required

Requires tasks.write.

Field Type Description
task_ids array Task ids, at least one, capped at 1,000
Bash
curl -X DELETE "https://dashboard.steamlabs.dev/api/v1/tasks" \
  -H "Authorization: Bearer $STEAMLABS_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"task_ids": ["019fb431-2c88-71ea-b0a3-8ce2f5d19b07"]}'
JSON
{ "deleted": 1, "skipped": 0 }

Unfinished tasks are flagged cancelled before the rows go, so the fleet drops the work instead of erroring on a missing row. Deleting a batch parent takes its children with it.

skipped counts ids that could not be deleted: children of a batch that has not finished (their results still feed the parent's counters), and ids that are not yours.

Clear finished tasks

DELETE/api/v1/tasks/finished

Delete every completed, failed, and cancelled top-level task.

API key required

Requires tasks.write. No request body.

Bash
curl -X DELETE "https://dashboard.steamlabs.dev/api/v1/tasks/finished" \
  -H "Authorization: Bearer $STEAMLABS_API_KEY"
JSON
{ "deleted": 42 }

The one-click history cleanup, matching the Tasks page's "Clear finished" button. Every finished top-level task goes, and a deleted batch parent takes its children with it (deleted counts top-level rows only). Queued and running tasks are never touched: to remove live work, cancel it or name its ids in Delete many tasks.

One task

GET/api/v1/tasks/{id}

One task in full, including the worker payload and its logs.

API key required

Requires tasks.read.

Bash
curl "https://dashboard.steamlabs.dev/api/v1/tasks/019fb440-7a10-7c33-bd51-2f0c9e4471d2" \
  -H "Authorization: Bearer $STEAMLABS_API_KEY"
JSON
{
    "id": "019fb440-7a10-7c33-bd51-2f0c9e4471d2",
    "type": "claim_weekly_drops",
    "status": "failed",
    "is_batch_parent": false,
    "is_cancelling": false,
    "parent_id": "019fb431-2c88-71ea-b0a3-8ce2f5d19b07",
    "steam_account_id": "019fb42e-9a61-70d2-818a-f6a56593f3a5",
    "steam_account": {
        "id": "019fb42e-9a61-70d2-818a-f6a56593f3a5",
        "username": "farm_017",
        "persona_name": "Ada",
        "steam64_id": "76561198000000017"
    },
    "children": { "total": 0, "completed": 0, "failed": 0, "cancelled": 0 },
    "origin": {
        "kind": "workflow",
        "label": "Automation",
        "detail": "Nightly refresh",
        "source": null,
        "source_label": null,
        "workflow_id": "019fb3f0-5e2a-7d1c-9c44-0f8a2b6d7e11",
        "workflow_run_id": "019fb431-2b70-7a05-8d2e-6c1f0a9b3c22",
        "api_key_id": null,
        "api_key_name": null,
        "retried_task_id": null
    },
    "error": "The Game Coordinator session timed out.",
    "worker_id": "worker-eu-14",
    "bytes_sent": 1204482,
    "bytes_received": 12044820,
    "cancel_requested_at": null,
    "dispatch_after": null,
    "started_at": "2026-07-30T14:02:44+00:00",
    "finished_at": "2026-07-30T14:04:02+00:00",
    "created_at": "2026-07-30T14:02:11+00:00",
    "updated_at": "2026-07-30T14:04:02+00:00",
    "task_data": { "strategy": "highest_price", "ignore_graffitis": true },
    "point_shop_purchase": null,
    "trade_up": null,
    "logs": [
        { "ts": 1785420164, "level": "info", "msg": "Signing in to Steam" },
        { "ts": 1785420191, "level": "info", "msg": "Launching CS2" },
        { "ts": 1785420242, "level": "error", "msg": "GC session timed out after 45s" }
    ],
    "worker_public_ip": "185.22.10.4",
    "proxy": { "id": "019fb42f-1188-70aa-8f2c-3b7de51a9c60", "host": "res-eu-01.example.net", "port": 8080 }
}

Five fields appear only here: task_data (the payload the worker was handed, already resolved per account), point_shop_purchase (the purchase summary and 20 most recent attempts, or null), trade_up (the trade-up result, or null), logs (the last 200 lines the worker sent back), and proxy (which proxy it went through).

For a trade_up_contract task that has reported back, trade_up is what the task page shows:

JSON
{
    "contracts": { "total": 24, "completed": 0, "failed": 24, "reconciled": 0 },
    "crafted": [],
    "failure_reasons": [
        { "error": "The CS2 Game Coordinator did not acknowledge the craft within 20s", "contracts": 3 },
        { "error": "Not attempted: the CS2 Game Coordinator stopped answering craft requests", "contracts": 21 }
    ]
}

crafted lists the market names of the skins produced, with null for one whose name is not known yet (it shows in the inventory after the refresh that follows every run). failure_reasons groups the failed contracts by error, most frequent first. It is null on batch parents and before the worker reported. The per-contract entries are in task_data.contract_results.

For buy_point_shop_items, point_shop_purchase.summary contains purchased, attempted, points_spent, and accounts, and point_shop_purchase.skipped contains unaffordable, not_owned, and not_eligible for the candidates that never became attempts. Each recent attempt includes its definition id, title, approved and live costs, balances, outcome, Steam eresult, Steam's own error_message (for a not_eligible attempt it names the game and says whether ownership or playtime was missing), grant ids, and timestamps. A batch parent rolls up every child that shares its purchase plan.

A task id that belongs to someone else returns 404, the same as an id that does not exist. We do not distinguish, because confirming an id exists elsewhere leaks that it exists.

A batch's children

GET/api/v1/tasks/{id}/children

A batch parent's per-account child tasks.

API key required

Requires tasks.read.

Parameter Type Description
status string One status value
search string Username, persona name or Steam64 id
origin string One origin kind (children normally share their parent's, so this mostly separates retried children)
origin_source string One automatic process
sort string created_at (default), started_at, finished_at
direction string asc, desc (default)
page, per_page integer Default 50 per page, clamped to 200
Bash
curl "https://dashboard.steamlabs.dev/api/v1/tasks/019fb431-2c88-71ea-b0a3-8ce2f5d19b07/children?status=failed" \
  -H "Authorization: Bearer $STEAMLABS_API_KEY"

Rows are the same shape the list endpoint returns. GET /api/v1/tasks?parent={id} gives you the identical set with the full filter vocabulary, if you need type or a date range as well.

Call this with status=failed to see which accounts failed and why. To act on them, use retry-failed on the parent rather than looping the children.

Retry a failed task

POST/api/v1/tasks/{id}/retry

Re-run one failed account-bound task.

API key required

Requires tasks.write. Requires an Idempotency-Key header.

No body. The task's own resolved task_data becomes the config, so a random persona name re-rolls and an already-planned trade-up re-dispatches verbatim.

Bash
curl -X POST "https://dashboard.steamlabs.dev/api/v1/tasks/019fb440-7a10-7c33-bd51-2f0c9e4471d2/retry" \
  -H "Authorization: Bearer $STEAMLABS_API_KEY" \
  -H "Idempotency-Key: $(uuidgen)"

Answers 201 with the new task, exactly as POST /api/v1/tasks does for a single account.

The task must be Failed, bound to a Steam account, and not a batch parent. Anything else gets 422 task_not_retryable. A failed child of a finished batch qualifies; the parent itself does not.

Points Shop purchases have a stricter rule. A task with a pending, purchased, or unknown attempt returns 409 point_shop_purchase_retry_unsafe. Create a new task after checking the account instead. This prevents a retry from buying the same reward twice after Steam completed an earlier request.

Retry a batch's failures

POST/api/v1/tasks/{id}/retry-failed

Re-run every failed child of a finished batch.

API key required

Requires tasks.write. Requires an Idempotency-Key header.

No body. The failed set travels as a filter, never as ids, so a 100,000-child batch does not have to materialize its failures before it can retry them.

Bash
curl -X POST "https://dashboard.steamlabs.dev/api/v1/tasks/019fb431-2c88-71ea-b0a3-8ce2f5d19b07/retry-failed" \
  -H "Authorization: Bearer $STEAMLABS_API_KEY" \
  -H "Idempotency-Key: $(uuidgen)"
JSON
{
    "task_id": "019fb44a-51e7-7ab8-9f30-6ca7ce31f008",
    "accounts_affected": 12,
    "accounts_skipped": 0
}

Always 202 with a new batch parent, because you cannot know how many children failed. The original parent is left alone.

The parent must be finished, have at least one failed child, and still carry the selection it was created with. Otherwise you get 422 task_not_retryable.

Points Shop purchase batches always return 409 point_shop_purchase_retry_unsafe here. Create a new task for the accounts you want to run again, so the catalogue and prices are frozen into a new plan.

Cancel one task

POST/api/v1/tasks/{id}/cancel

Ask a task to stop.

API key required

Requires tasks.write. No body, and no Idempotency-Key: cancelling twice changes nothing.

Cancelling is a request, not a transition, which is why this answers 200 with the record rather than 204:

  • a Waiting or Queued task flips to Cancelled straight away;
  • a Running task only gets cancel_requested_at stamped, and the worker decides the final status. It may still finish and report Completed.

Read status and cancel_requested_at off the response to tell which of the two happened. While the request is outstanding, is_cancelling is true.

Bash
curl -X POST "https://dashboard.steamlabs.dev/api/v1/tasks/019fb431-2c88-71ea-b0a3-8ce2f5d19b07/cancel" \
  -H "Authorization: Bearer $STEAMLABS_API_KEY"

Cancelling a batch parent cancels its children. Only top-level unfinished tasks with no cancel already pending can be cancelled; anything else gets 422 task_not_cancellable.

Delete one task

DELETE/api/v1/tasks/{id}

Delete a task. Returns 204.

API key required

Requires tasks.write.

An unfinished task is flagged cancelled before the row goes, so the fleet drops the work. A batch parent takes its children with it.

A child of an unfinished batch cannot be deleted on its own: its result feeds the parent's counters, and removing the row would strand the parent running forever. That is 422 task_not_deletable. Cancel or delete the parent instead.

Errors

On top of the universal codes, this group returns:

Status Code Meaning
422 no_eligible_accounts Nobody in the selection could take this task type. Adds accounts_skipped and, where the pipeline attributes its skips, accounts_skipped_reasons
422 nothing_to_plan The planner matched no items for distribute_items, sell_items or a storage type
422 proxies_required You own no proxies and hold more accounts than the shared pool covers
422 task_not_retryable Not a failed account-bound task, or not a finished batch with failed children and a stored selection
422 task_not_cancellable Already finished, already cancelling, or a child covered by cancelling its parent
422 task_not_deletable A child of a batch that has not finished yet
422 bulk_limit_exceeded More than 1,000 ids in account_ids, exclude_account_ids or task_ids. Adds max
409 point_shop_purchase_retry_unsafe A Points Shop task may already have bought something, or a Points Shop batch was sent to retry-failed. Create a new task after checking the accounts
403 plan_limit_reached Your plan's allowed_task_types or allowed_marketplaces does not cover this. Adds plan with the upgrade path
503 maintenance_mode Platform maintenance pauses creation (POST /api/v1/tasks, retry, retry-failed). Reads, cancels and deletes keep working. Adds reason
400 idempotency_key_required POST /api/v1/tasks, retry and retry-failed need the header

Everything else (validation_failed, not_found, missing_scope, rate_limit_exceeded, the idempotency conflicts) behaves as described on Errors.

Validation failures on a config report per field under config.*, so a bad sell mode reads as config.mode rather than a bespoke envelope:

JSON
{
    "message": "The given data was invalid.",
    "code": "validation_failed",
    "errors": {
        "config.mode": ["The buy_order pricing mode is only available on the steam marketplace."],
        "config.confirm_sell_all": ["This config has no item condition and no cap, so it would list every marketable item on every selected account. Set confirm_sell_all to true to accept that."]
    }
}