Endpoints
Connected services
Connect marketplaces, manage the Steam Web API key pool, and read the state of every third-party link from code.
Everything the Marketplaces tab and the Web API keys page do, over HTTP: connect a selling venue for an account, arm or disarm selling, tune how often SteamLabs polls it, and keep the Steam Web API key pool topped up.
One pair of scopes covers both: integrations.read for the state, integrations.write for everything that changes it.
Credentials only go in
No response on this page ever contains a stored credential. Not the marketplace API key, not a Steam Web API key, not on the write that just stored it.
That is deliberate, and it is a stricter rule than the panel's. In a browser you are one person at one keyboard; an API key gets pasted into scripts, shared with a contractor and committed by accident, and a Steam Web API key exfiltrated through a read-only token is a live Valve credential bound to a real Steam account. What you get instead is state: whether a key is stored, whether the venue has accepted it, whether selling is armed, and what the venue last reported. Steam Web API keys come back as masked_key, showing the last four characters so you can tell two rows apart.
Send a new key to replace one. There is no way to read one back.
Marketplace connections
A connection links one of your Steam accounts to one selling venue. Two venues are connectable: csfloat and marketcsgo. Steam is not, and never will be: its "connection" is the Steam account's own session, so there is nothing to paste, verify or revoke.
Connections are state only. Listings, sales, buy orders and deliveries live under /api/v1/market/{venue} behind the market.* scopes, so a reporting integration never needs a token that can disconnect a marketplace.
List connections
/api/v1/integrations/marketplacesEvery marketplace connection you have, connected or not.
| Filter | Values |
|---|---|
marketplace |
csfloat, marketcsgo |
steam_account_id |
One of your Steam account IDs |
connected |
true keeps rows that hold a key |
enabled |
true keeps rows where selling is armed |
sort |
venue (default), newest, oldest, last_synced |
curl "https://dashboard.steamlabs.dev/api/v1/integrations/marketplaces?marketplace=csfloat" \
-H "Authorization: Bearer $STEAMLABS_API_KEY"{
"data": [
{
"id": "019fb42e-9a61-70d2-818a-f6a56593f3a5",
"marketplace": "csfloat",
"connected": true,
"verified": true,
"enabled": true,
"auto_deliver_external": true,
"auto_credit_settlements": false,
"account_label": "farm_014",
"external_user_id": "76561198000000000",
"balance_cents": 48210,
"pending_balance_cents": 1200,
"currency": "USD",
"fee_bps": 200,
"kyc_status": "verified",
"steam_account": { "id": "019fb42e-9a7e-728d-b960-8b4c2162898c", "username": "farm_014" },
"proxy": { "id": "019fb430-1c22-73a4-9f0e-2b7c5d1e8a44", "protocol": "http", "host": "res-eu-01.example.net", "port": 8080, "type": "static" },
"poller_settings": { "hot_seconds": 30, "warm_seconds": 120, "cold_seconds": 900, "hot_cooldown_seconds": 600 },
"linked_at": "2026-07-01T09:15:00+00:00",
"last_synced_at": "2026-07-30T14:02:11+00:00",
"poll_health": {
"healthy": true,
"consecutive_failures": 0,
"last_error_kind": null,
"last_error": null,
"last_error_at": null
},
"created_at": "2026-07-01T09:12:00+00:00",
"updated_at": "2026-07-30T14:02:11+00:00"
}
],
"meta": { "page": 1, "per_page": 50, "total": 1, "last_page": 1 }
}Three fields say three different things and they fail separately:
connected: a key is stored.verified: the venue has accepted that key at least once. Selling cannot be armed before this istrue.enabled: selling is armed.
Rows with connected: false are kept rather than deleted. Disconnecting drops the key and leaves the row, carrying the last balance and fee the venue reported, so reconnecting later picks up where you left off.
account_label is the venue's own name for the account. It is never a credential.
Knowing a connection is broken
poll_health is the field to alert on. last_synced_at cannot tell you what you want to know: it stops advancing when a connection breaks, which looks exactly like an account nobody is trading on.
| Field | Meaning |
|---|---|
healthy |
false once enough polls have failed in a row to count as an outage rather than a blip |
consecutive_failures |
Failed polls since the last good one, reset to 0 by the next successful sync |
last_error_kind |
proxy, auth, rate_limited, network, http or unknown |
last_error |
The raw failure text, truncated. For humans, not for branching |
last_error_at |
When the most recent failure happened |
Branch on last_error_kind, not on last_error. Two of them mean something is wrong that only you can fix:
proxy: the dedicated proxy pinned to this connection refused the tunnel. Out of bandwidth and expired plans both land here. The venue never saw the request.auth: the venue rejected the API key. It needs replacing.
The rest (rate_limited, network, http) clear on their own and are not worth waking anyone for.
One connection
/api/v1/integrations/marketplaces/{id}One connection, plus its readiness checklist and poller bounds.
Same shape as a list row, with two extras. readiness is the same checklist the panel draws beside the connection card, every condition evaluated independently so you can tell a user exactly what is still missing:
{
"readiness": {
"key_present": true,
"enabled": true,
"identity_secret": true,
"dedicated_proxy": true,
"proxy": true
},
"poller_limits": {
"minimums": { "hot_seconds": 30, "warm_seconds": 120, "cold_seconds": 900, "hot_cooldown_seconds": 600 },
"maximums": {}
}
}poller_limits are the bounds your cadences are clamped to, so you can build a form without discovering them by being clamped.
Connect a venue
/api/v1/integrations/marketplaces/{venue}/connectStore an API key for one Steam account and queue its verification.
The venue is in the path, matching /api/v1/market/{venue}, so there is no default venue to fall into.
curl -X POST "https://dashboard.steamlabs.dev/api/v1/integrations/marketplaces/csfloat/connect" \
-H "Authorization: Bearer $STEAMLABS_API_KEY" \
-H "Idempotency-Key: $(uuidgen)" \
-H "Content-Type: application/json" \
-d '{
"steam_account_id": "019fb42e-9a7e-728d-b960-8b4c2162898c",
"api_key": "your-csfloat-api-key",
"proxy_id": "019fb430-1c22-73a4-9f0e-2b7c5d1e8a44"
}'201 when this armed a connection that was not armed before, 200 when it replaced a working key. Both return the connection.
A dedicated static proxy is required, and it is the one thing you cannot skip. Both venues expect every request for an account to come from one address, and a rotating proxy changes IP per request, which is what gets an account rate limited. Pass proxy_id on the first connect; a reconnect keeps the pinned one if you omit it. See Proxies for creating one.
This endpoint requires an Idempotency-Key: it stores a credential and queues work, so a retry after a timeout must replay rather than repeat.
| Refusal | Meaning |
|---|---|
422 proxy_required |
No dedicated proxy pinned and none supplied |
422 proxy_static_only |
The proxy is not a static one |
422 key_required |
No key in the body |
403 plan_limit_reached |
Your plan does not sell on this venue; the response carries the upgrade path |
404 unknown_venue |
No such marketplace |
404 venue_unsupported |
A real venue with nothing to connect (Steam) |
Change settings
/api/v1/integrations/marketplaces/{id}Selling switch, automation toggles, dedicated proxy and poller cadences.
Everything the panel spreads over three toggles, a proxy picker and an advanced settings form, in one call. Every field is optional and only the ones you send are touched, so flipping one toggle can never clear settings you did not mention.
| Field | Meaning |
|---|---|
enabled |
Arm or disarm selling on this venue |
auto_deliver_external |
Accept and deliver sales from listings made outside SteamLabs |
auto_credit_settlements |
Credit a sale once it clears its trade-protection hold (CSFloat) |
proxy_id |
Repin the dedicated static proxy |
poller_settings |
How often SteamLabs polls this account, in seconds |
curl -X PATCH "https://dashboard.steamlabs.dev/api/v1/integrations/marketplaces/019fb42e-9a61-70d2-818a-f6a56593f3a5" \
-H "Authorization: Bearer $STEAMLABS_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "enabled": true, "poller_settings": { "hot_seconds": 45 } }'Arming selling on a key that has never verified answers 422 verify_before_enabling. Switching selling off is never refused, whatever state the row is in.
Cadences are merged over what you already have and clamped to the venue's bounds rather than rejected: you may only slow polling down, and market.csgo's selling_seconds also has a ceiling, because that tick doubles as the presence ping and the venue's window is 180 seconds. The response returns the effective settings, so you can always see what you actually got.
Sync a connection
/api/v1/integrations/marketplaces/{id}/syncRe-check the key and refresh the balance, label, fee and KYC snapshot.
The panel's Sync now. Answers 202 with the queued task:
{ "task_id": "019fb431-2c88-71ea-b0a3-8ce2f5d19b07", "accounts_affected": 1 }No Idempotency-Key needed: this creates no listing, moves no item and spends nothing, and running it twice gives you the same snapshot. A connection with no key answers 422 not_connected.
Disconnect
/api/v1/integrations/marketplaces/{id}Drop the stored key and stop selling.
Answers 204. Listings, sales and history are kept: they describe things that really happened on a real marketplace. The connection row survives too, so reconnecting later is one call.
Steam Web API keys
The pool behind every Steam Web API read SteamLabs makes for you. Keys arrive two ways: an account's details refresh mints one (source: auto), or you paste one in (source: manual).
List keys
/api/v1/integrations/steam-web-api-keysYour key pool, paginated and filterable.
| Filter | Values |
|---|---|
status |
active, cooldown, disabled |
source |
auto, manual |
steam_account_id |
The account whose session minted the key |
active |
true or false, the raw is_active flag |
sort |
newest (default), oldest, last_used, requests_today |
{
"data": [
{
"id": "019fb42e-9a61-70d2-818a-f6a56593f3a5",
"masked_key": "********6789",
"source": "auto",
"is_active": true,
"status": "active",
"steam_account": { "id": "019fb42e-9a7e-728d-b960-8b4c2162898c", "username": "farm_014" },
"steam64_id": "76561198000000000",
"domain": null,
"requests_today": 412,
"usage_date": "2026-07-30",
"cooldown_until": null,
"last_used_at": "2026-07-30T15:41:52+00:00",
"last_error": null,
"disabled_reason": null,
"created_at": "2026-07-01T09:15:00+00:00",
"updated_at": "2026-07-30T15:41:52+00:00"
}
],
"meta": { "page": 1, "per_page": 50, "total": 1, "last_page": 1 }
}status is derived, exactly as the panel's badge is: disabled (switched off, or revoked) beats cooldown (benched until cooldown_until after a rate limit) beats active. Filtering on it filters in SQL, so the totals mean what they say.
Add a key
/api/v1/integrations/steam-web-api-keysPaste a Steam Web API key into the pool.
curl -X POST "https://dashboard.steamlabs.dev/api/v1/integrations/steam-web-api-keys" \
-H "Authorization: Bearer $STEAMLABS_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "key": "ABCDEF0123456789ABCDEF0123456789" }'Answers 201 with the key row, masked. source is stamped manual: it records where a key came from, so you cannot claim one was minted by a session that never ran. Keys are unique platform-wide, because a key is a Valve credential bound to one Steam account and two owners sharing one would share a quota.
Enable or disable a key
/api/v1/integrations/steam-web-api-keys/{id}Switch one key on or off.
is_active is the only writable field. Everything else on the row is the pool's own notes on a key's health.
Re-enabling gives the key a clean slate: the cooldown, the last error and the disable reason are cleared. Without that, a key you switched on would still be skipped, which reads as the toggle not working.
Delete keys
/api/v1/integrations/steam-web-api-keys/{id}Remove one key from the pool.
/api/v1/integrations/steam-web-api-keys/deleteRemove several keys at once.
The single delete answers 204. The bulk form takes key_ids and answers with what actually went:
{ "keys_deleted": 12 }IDs that are not yours are simply not matched, so a partly-foreign list is not an error. Explicit IDs only, capped at 1,000; over that you get 422 bulk_limit_exceeded with the ceiling in max. There is no filter mode here, because a key pool is a handful of rows you can list in one page, and a mistyped filter should not be able to empty it.
Pool settings
/api/v1/integrations/steam-web-api-keys/settingsThe pool target, auto-create switch and current counts.
/api/v1/integrations/steam-web-api-keys/settingsSet the pool target and auto-create switch.
{
"steam_web_api_key_pool_target": 10,
"steam_web_api_key_auto_create": true,
"keys_total": 7,
"keys_active": 6
}These two settings live on your account rather than on any key, which is why they are a sub-resource of the collection. steam_web_api_key_pool_target is how many keys the pool aims to hold (0 stops topping it up; the keys already in it keep working), and steam_web_api_key_auto_create decides whether details refreshes may mint new ones to get there. The counts are returned with them because a target of ten says nothing without knowing the pool holds three.
A PUT rather than a PATCH: there are two fields, they are always shown together, and both are required so a read-then-write cannot half-apply.
Not on the API
Linking a FarmLabs account, and its sync runs. The link is an OAuth-style browser handshake whose entire security model is the interactive consent step, and a sync run is a staged, reviewed import with an admin-shaped surface behind it. Neither survives being reduced to a bearer-token call, so both stay in the panel.