Endpoints
Market
Listings, buy orders, sales history and deliveries on Steam, CSFloat and market.csgo.
Everything the Market pages do, over HTTP. The venue is part of the path: every route lives under /api/v1/market/{venue}/, where venue is steam, csfloat or marketcsgo.
There is no default venue, so no request can quietly read Steam rows while you believed it was reading CSFloat. An unknown venue answers 404 with code unknown_venue.
Two scopes cover the domain. market.read for the listings, orders, history, sales and stats. market.write for everything that changes them.
Venues
The three venues do not carry the same surfaces. A surface a venue genuinely lacks answers 404 with code venue_unsupported, not 403: the resource does not exist there, it is not forbidden there.
| Surface | steam |
csfloat |
marketcsgo |
|---|---|---|---|
| Stats | Yes | Yes | Yes |
| Listings: list, cancel, sync | Yes | Yes | Yes |
| Reprice a listing | No | Yes | Yes |
| Buy orders | Yes | No | No |
| Sales history | Yes | Yes | Yes |
| Sale deliveries (trades) | No | Yes | Yes |
| Accept a sale | No | Yes | No |
| Cancel a sale | No | Yes | No |
Steam fixes a listing's price at creation, so there is nothing to reprice. CSFloat and market.csgo are sell-only, so they have no buy orders. Steam settles a sale internally the moment it happens, so it has no delivery stage: on the other two the marketplace only brokers the deal and you still have to send the item.
What your plan gates
Your subscription plan controls which venues you may sell on. It never gates reads, and it never gates pulling your own items back.
Listing, delisting, repricing, syncing, reading history and delivering a sale all work on every venue, whatever your plan says. Someone who downgraded still has real items on a real marketplace, and an API that hid them would strand inventory they cannot get back.
Two writes here can still come back 403 plan_limit_reached:
- Placing a buy order. Buying spends the Steam venue entitlement, so it needs
steamin your plan'sallowed_marketplaces, plusplace_buy_orderinallowed_task_types. - Syncing on Steam. A Steam refresh is a real task, so it needs
sync_market_listings(orsync_market_history) inallowed_task_types. The CSFloat and market.csgo refreshes are poller requests and are never gated.
Read plan from GET /api/v1/me at startup and you will rarely meet either. See Errors for the response shape.
Venue stats
The headline numbers above a venue's tables.
/api/v1/market/{venue}/statsOpen listings, recent money moved, and where your balance sits on this venue.
Requires market.read.
curl https://dashboard.steamlabs.dev/api/v1/market/steam/stats \
-H "Authorization: Bearer $STEAMLABS_API_KEY"{
"venue": "steam",
"window_days": 30,
"listings": {
"open_count": 184,
"open_value_cents": { "EUR": 412300, "USD": 88150 },
"sold_recent_cents": { "EUR": 96420, "USD": 12005 }
},
"buy_orders": {
"open_count": 12,
"committed_value_cents": { "EUR": 31400 },
"bought_recent_cents": { "EUR": 18720 }
},
"wallet_cents": { "EUR": 24150, "PLN": 9800 },
"marketplace_balance": null
}On CSFloat and market.csgo the shape stays the same, with buy_orders and wallet_cents null and the venue-side balance filled in:
{
"venue": "csfloat",
"window_days": 30,
"listings": {
"open_count": 62,
"open_value_cents": { "USD": 148900 },
"sold_recent_cents": { "USD": 51230 }
},
"buy_orders": null,
"wallet_cents": null,
"marketplace_balance": {
"available_cents": { "USD": 21400 },
"pending_cents": { "USD": 6800 }
}
}window_days is 30, and it applies to sold_recent_cents and bought_recent_cents only. Sales are summed from history events rather than from listings, because an item that sells instantly by crossing a standing buy order never lingers as an open listing.
committed_value_cents is what your open buy orders still hold: per-unit price times the quantity not yet filled.
List listings
What you have up for sale on this venue, and what has happened to it.
/api/v1/market/{venue}/listingsYour listings on this venue, paginated and filterable.
Requires market.read.
| Parameter | Type | Description |
|---|---|---|
state |
string | open, sold, or canceled. open covers pending_confirmation, active and on_hold. canceled covers both a listing you pulled and one that vanished from an authoritative sync |
account_id |
uuid | Only listings from this Steam account. Must be an account you own, otherwise 422 |
origin |
string | steamlabs for listings this platform created, external for ones found already on the venue |
game |
string | cs2, tf2, or steam |
appid |
integer | The Steam app id, if you already hold the number. Wins over game when both are sent |
search |
string | Matches the item name or its market hash name |
sort |
string | listed_at (default), resolved_at, buyer_pays_cents, created_at |
direction |
string | asc or desc (default) |
per_page |
integer | 50 by default, 200 at most |
curl "https://dashboard.steamlabs.dev/api/v1/market/steam/listings?state=open&game=cs2&per_page=1" \
-H "Authorization: Bearer $STEAMLABS_API_KEY"{
"data": [
{
"id": "019fb42e-9a61-70d2-818a-f6a56593f3a5",
"listing_id": "4258109377312946881",
"marketplace": "steam",
"origin": "steamlabs",
"state": "active",
"is_open": true,
"steam_account": {
"id": "019fb42e-9a7e-728d-b960-8b4c2162898c",
"username": "farm_017"
},
"asset_id": "38294011745",
"appid": 730,
"game": "cs2",
"context_id": "2",
"market_hash_name": "AK-47 | Redline (Field-Tested)",
"name": "AK-47 | Redline",
"icon_url": "https://community.fastly.steamstatic.com/economy/image/…",
"amount": 1,
"buyer_pays_cents": 1842,
"receive_cents": 1602,
"fee_cents": 240,
"sold_receive_cents": null,
"currency": "EUR",
"listed_at": "2026-07-28T11:40:02+00:00",
"resolved_at": null,
"created_at": "2026-07-28T11:40:05+00:00"
}
],
"meta": { "page": 1, "per_page": 1, "total": 184, "last_page": 184 }
}buyer_pays_cents is what the listing shows a buyer. receive_cents is your net after the venue's fee, and sold_receive_cents fills in once it actually sells. is_open saves you mapping the six raw states yourself.
Refresh listings
Re-read the venue for the accounts you name.
/api/v1/market/{venue}/listings/syncPull fresh listings, wallet and buy orders from the venue.
Requires market.write.
| Field | Type | Description |
|---|---|---|
account_ids |
uuid[] | Accounts to refresh, up to 1,000. Leave it out for the whole eligible fleet |
A sync is account-oriented, never listing-oriented: the venue is asked for an account's whole state at once. That is why an absent account_ids is safe here and nowhere else in this domain. It creates no listings, spends no money and moves no items.
curl -X POST "https://dashboard.steamlabs.dev/api/v1/market/steam/listings/sync" \
-H "Authorization: Bearer $STEAMLABS_API_KEY" \
-H "Content-Type: application/json" \
-d '{"account_ids":["019fb42e-9a7e-728d-b960-8b4c2162898c"]}'On Steam a refresh is a task, so you get a task to follow:
{
"task_id": "019fb440-1c22-73a4-9f0e-2b7c5d1e8a44",
"accounts_affected": 412,
"async": false,
"accounts_skipped": 18
}async is true above 500 accounts, where the batch is built by a background job. accounts_skipped counts accounts left out because they already had market work queued.
On CSFloat and market.csgo there is no task. Those venues are polled continuously over HTTP, so a refresh asks the poller to visit those accounts now instead of on its cadence:
{
"task_ids": [],
"accounts_affected": 34,
"sync": "poll_requested"
}Answers 422 nothing_eligible when nothing matched.
Cancel many listings
Pull a selection of listings off the marketplace.
/api/v1/market/{venue}/listings/cancelDelist many listings, grouped into one task per account.
Requires market.write.
| Field | Type | Description |
|---|---|---|
listing_ids |
uuid[] | The listings to pull, up to 1,000. Required unless you send filters |
filters |
object | The same filter keys the listings index accepts, resolved server-side. Required unless you send listing_ids |
Send one or the other, never neither. An empty body delisting your entire market is exactly the accident an API should refuse. See Bulk operations for the shared contract.
curl -X POST "https://dashboard.steamlabs.dev/api/v1/market/csfloat/listings/cancel" \
-H "Authorization: Bearer $STEAMLABS_API_KEY" \
-H "Content-Type: application/json" \
-d '{"filters":{"state":"open","game":"cs2"}}'$result = Http::withToken($apiKey)
->post('https://dashboard.steamlabs.dev/api/v1/market/csfloat/listings/cancel', [
'filters' => ['state' => 'open', 'game' => 'cs2'],
])
->json();const response = await fetch('https://dashboard.steamlabs.dev/api/v1/market/csfloat/listings/cancel', {
method: 'POST',
headers: {
Authorization: `Bearer ${apiKey}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({ filters: { state: 'open', game: 'cs2' } }),
});
const result = await response.json();result = requests.post(
"https://dashboard.steamlabs.dev/api/v1/market/csfloat/listings/cancel",
headers={"Authorization": f"Bearer {api_key}"},
json={"filters": {"state": "open", "game": "cs2"}},
).json()Answers 202, with one task per account rather than a single parent:
{
"task_ids": [
"019fb441-2c88-71ea-b0a3-8ce2f5d19b07",
"019fb441-3d19-7285-9c41-72b0e4a9d233"
],
"accounts_affected": 2,
"listings_affected": 47,
"accounts_busy": 1
}listings_affected is counted off the tasks that were really created, so an account the single-flight guard refused is reported in accounts_busy instead of being counted as queued.
Listings that are already closed are dropped rather than refused, so a sale that lands between reading the page and pressing the button does not fail the other forty-nine. If none of the selection is still open you get 422 nothing_to_cancel with matched.
Cancel one listing
/api/v1/market/{venue}/listings/{listing}/cancelPull one listing off the marketplace.
Requires market.write.
curl -X POST "https://dashboard.steamlabs.dev/api/v1/market/steam/listings/019fb42e-9a61-70d2-818a-f6a56593f3a5/cancel" \
-H "Authorization: Bearer $STEAMLABS_API_KEY"{
"task_ids": ["019fb441-2c88-71ea-b0a3-8ce2f5d19b07"],
"accounts_affected": 1,
"listings_affected": 1
}202, not 204. The venue is reached by a worker holding the account's session, so the listing keeps its current state until the task comes back. Reporting 204 would tell you the item is already delisted, which is the lie that makes an integration relist it twice.
A listing that is no longer open answers 422 listing_not_open with its state. An account that already has market work in flight answers 409 account_busy; wait for that task and try again. A listing id that belongs to another venue answers a plain 404.
Reprice a listing
Change what a live listing asks a buyer to pay.
/api/v1/market/{venue}/listings/{listing}/priceSet a new buyer-pays price. CSFloat and market.csgo only.
Requires market.write.
| Field | Type | Description |
|---|---|---|
price_cents |
integer | The new buyer-pays price in cents. At least 1, at most 100000000 |
curl -X POST "https://dashboard.steamlabs.dev/api/v1/market/csfloat/listings/019fb42e-9a61-70d2-818a-f6a56593f3a5/price" \
-H "Authorization: Bearer $STEAMLABS_API_KEY" \
-H "Content-Type: application/json" \
-d '{"price_cents":1795}'{
"task_ids": ["019fb442-8a10-7c33-b4d2-0f1e6c7a9b55"],
"accounts_affected": 1,
"listings_affected": 1,
"price_cents": 1795
}/market/steam/listings/{id}/price answers 404 venue_unsupported. A Steam listing's price is fixed at creation, and the equivalent is a delist plus a fresh listing, which costs a fee and a new asset id. Doing that silently under a "change price" call would surprise every caller exactly once.
List buy orders
Standing bids resting on the Steam market, waiting to be filled.
/api/v1/market/{venue}/buy-ordersYour buy orders. Steam only.
Requires market.read.
| Parameter | Type | Description |
|---|---|---|
state |
string | open, canceled, or closed. closed means it vanished from an authoritative sync: filled or cancelled outside our sight |
account_id |
uuid | Only orders from this Steam account. Must be one you own |
game |
string | cs2, tf2, or steam |
appid |
integer | The Steam app id. Wins over game when both are sent |
search |
string | Matches the item name or its market hash name |
sort |
string | created_at (default), buyer_pays_per_unit_cents, closed_at |
direction |
string | asc or desc (default) |
per_page |
integer | 50 by default, 200 at most |
curl "https://dashboard.steamlabs.dev/api/v1/market/steam/buy-orders?state=open&per_page=1" \
-H "Authorization: Bearer $STEAMLABS_API_KEY"{
"data": [
{
"id": "019fb443-6b71-70aa-8f3c-51d0c9e4a712",
"buy_order_id": "6412887301",
"state": "open",
"steam_account": {
"id": "019fb42e-9a7e-728d-b960-8b4c2162898c",
"username": "farm_017"
},
"appid": 730,
"game": "cs2",
"market_hash_name": "Fracture Case",
"name": "Fracture Case",
"icon_url": "https://community.fastly.steamstatic.com/economy/image/…",
"buyer_pays_per_unit_cents": 21,
"currency": "EUR",
"quantity": 250,
"quantity_remaining": 187,
"quantity_filled": 63,
"closed_at": null,
"created_at": "2026-07-24T08:12:44+00:00"
}
],
"meta": { "page": 1, "per_page": 1, "total": 12, "last_page": 12 }
}On CSFloat and market.csgo this answers 404 venue_unsupported. Those venues are sell-only.
Place a buy order
Rest one bid across a selection of accounts.
/api/v1/market/{venue}/buy-ordersPlace one buy order across a selection of accounts. Steam only.
Requires market.write.
| Field | Type | Description |
|---|---|---|
market_hash_name |
string | The exact market hash name to bid on. Up to 255 characters |
game |
string | cs2, tf2, or steam |
prices_cents |
object | Per-unit buyer-pays price in cents, keyed by ISO wallet currency. At least one entry, each at least 3 cents |
quantity |
integer | Units per order. 1 by default, 1,000 at most |
account_ids |
uuid[] | Accounts to place on, up to 1,000. Leave it out for the whole eligible fleet |
curl -X POST "https://dashboard.steamlabs.dev/api/v1/market/steam/buy-orders" \
-H "Authorization: Bearer $STEAMLABS_API_KEY" \
-H "Idempotency-Key: $(uuidgen)" \
-H "Content-Type: application/json" \
-d '{"game":"cs2","market_hash_name":"Fracture Case","quantity":250,"prices_cents":{"EUR":21,"PLN":92}}'$result = Http::withToken($apiKey)
->withHeaders(['Idempotency-Key' => (string) Str::uuid()])
->post('https://dashboard.steamlabs.dev/api/v1/market/steam/buy-orders', [
'game' => 'cs2',
'market_hash_name' => 'Fracture Case',
'quantity' => 250,
'prices_cents' => ['EUR' => 21, 'PLN' => 92],
])
->json();const response = await fetch('https://dashboard.steamlabs.dev/api/v1/market/steam/buy-orders', {
method: 'POST',
headers: {
Authorization: `Bearer ${apiKey}`,
'Idempotency-Key': crypto.randomUUID(),
'Content-Type': 'application/json',
},
body: JSON.stringify({
game: 'cs2',
market_hash_name: 'Fracture Case',
quantity: 250,
prices_cents: { EUR: 21, PLN: 92 },
}),
});
const result = await response.json();result = requests.post(
"https://dashboard.steamlabs.dev/api/v1/market/steam/buy-orders",
headers={
"Authorization": f"Bearer {api_key}",
"Idempotency-Key": str(uuid.uuid4()),
},
json={
"game": "cs2",
"market_hash_name": "Fracture Case",
"quantity": 250,
"prices_cents": {"EUR": 21, "PLN": 92},
},
).json()Answers 202, one task per currency group:
{
"task_ids": [
"019fb444-9e02-72c1-b7a8-3d5e1f0b6c49",
"019fb444-9e33-7410-84be-c1a72d9f5e80"
],
"accounts_affected": 128,
"async": false,
"accounts_underfunded": 14,
"accounts_busy": 2,
"accounts_unknown_currency": 3,
"selection_currencies": ["EUR", "PLN"]
}202 rather than 201 is not a technicality: nothing exists yet. Steam only knows about an order once a worker has logged the account in and placed it. The order rows appear later, through the sync that confirms them, so watch the returned tasks to know when.
The two skip counts need different fixes. accounts_underfunded means Steam's cap of ten times the wallet balance would reject the order, so add funds or lower the price. accounts_busy means the account already had market work in flight, and the same submission works again in a minute. accounts_unknown_currency counts accounts whose wallet currency has never been synced, so they could not be priced at all.
Only accounts that can actually place are targeted, named or not: market access allowed, an identity secret stored, and a billing address on file.
Refusals:
| Status | Code | Meaning |
|---|---|---|
422 |
missing_price_for_currency |
A currency in the selection had no price. Adds missing_currencies and selection_currencies. Nothing was placed |
422 |
nothing_eligible |
No selected account could fund the order. Adds the four counts above |
403 |
plan_limit_reached |
Your plan does not sell on Steam (allowed_marketplaces) or does not include place_buy_order (allowed_task_types) |
Refresh buy orders
/api/v1/market/{venue}/buy-orders/syncPull fresh buy orders from Steam. Steam only.
Requires market.write.
Takes the same optional account_ids as the listings refresh and answers the same 202 envelope with task_id, accounts_affected, async and accounts_skipped.
curl -X POST "https://dashboard.steamlabs.dev/api/v1/market/steam/buy-orders/sync" \
-H "Authorization: Bearer $STEAMLABS_API_KEY" \
-H "Content-Type: application/json" \
-d '{}'Cancel many buy orders
/api/v1/market/{venue}/buy-orders/cancelCancel many resting orders, grouped into one task per account. Steam only.
Requires market.write.
| Field | Type | Description |
|---|---|---|
buy_order_ids |
uuid[] | The orders to cancel, up to 1,000. Required unless you send filters |
filters |
object | The same filter keys the buy orders index accepts. Required unless you send buy_order_ids |
curl -X POST "https://dashboard.steamlabs.dev/api/v1/market/steam/buy-orders/cancel" \
-H "Authorization: Bearer $STEAMLABS_API_KEY" \
-H "Content-Type: application/json" \
-d '{"filters":{"state":"open","search":"Fracture Case"}}'{
"task_ids": ["019fb445-4411-7b90-9d0c-6e2a5f38c114"],
"accounts_affected": 1,
"buy_orders_affected": 9,
"accounts_busy": 0
}Cancelling frees the wallet balance an open order commits, so it is never plan-gated and never needs an Idempotency-Key. Its target state is "not resting", and a repeat is a no-op.
Orders that are no longer open are dropped. If none of the selection is still open you get 422 nothing_to_cancel with matched. A filter matching more than 1,000 orders answers 422 bulk_limit_exceeded.
Cancel one buy order
/api/v1/market/{venue}/buy-orders/{buyOrder}/cancelCancel one resting order. Steam only.
Requires market.write.
curl -X POST "https://dashboard.steamlabs.dev/api/v1/market/steam/buy-orders/019fb443-6b71-70aa-8f3c-51d0c9e4a712/cancel" \
-H "Authorization: Bearer $STEAMLABS_API_KEY"{
"task_ids": ["019fb445-77c3-7f21-a0b6-9c4e0d1a8b62"],
"accounts_affected": 1,
"buy_orders_affected": 1
}An order that is not open answers 422 buy_order_not_open with its state. An account with market work already in flight answers 409 account_busy.
Sales history
What has already happened on this venue: listed, cancelled, sold, bought, plus the marketplace-only lifecycle steps.
/api/v1/market/{venue}/historyMarket history events on this venue, newest first.
Requires market.read.
| Parameter | Type | Description |
|---|---|---|
event_type |
integer | One of the codes below |
account_id |
uuid | Only events from this Steam account. Must be one you own |
game |
string | cs2, tf2, or steam |
appid |
integer | The Steam app id. Wins over game when both are sent |
search |
string | Matches the item name or its market hash name |
sort |
string | happened_at (default), created_at |
direction |
string | asc or desc (default) |
per_page |
integer | 50 by default, 200 at most |
event_type is the stored integer. Codes 1 to 4 are Steam's own history codes, 10 to 12 are marketplace lifecycle transitions.
| Code | event_type_key |
Meaning |
|---|---|---|
1 |
listing_created |
An item went up for sale |
2 |
listing_canceled |
A listing was pulled |
3 |
listing_sold |
A listing sold |
4 |
purchased |
You bought an item |
10 |
delivered |
The item reached the buyer |
11 |
settled |
The venue released the funds |
12 |
failed |
The lifecycle step failed |
curl "https://dashboard.steamlabs.dev/api/v1/market/steam/history?event_type=3&per_page=1" \
-H "Authorization: Bearer $STEAMLABS_API_KEY"{
"data": [
{
"id": "019fb446-1a05-7c88-9f42-88b1e0c3d975",
"event_type": 3,
"event_type_key": "listing_sold",
"marketplace": "steam",
"steam_account": {
"id": "019fb42e-9a7e-728d-b960-8b4c2162898c",
"username": "farm_017"
},
"listing_id": "4258109377312946881",
"purchase_id": null,
"actor_steam64_id": "76561198000000042",
"asset_id": "38294011745",
"new_asset_id": "38301884012",
"appid": 730,
"game": "cs2",
"context_id": "2",
"market_hash_name": "AK-47 | Redline (Field-Tested)",
"name": "AK-47 | Redline",
"icon_url": "https://community.fastly.steamstatic.com/economy/image/…",
"listed_price_cents": 1842,
"paid_amount_cents": 1842,
"paid_fee_cents": 240,
"received_amount_cents": 1602,
"currency": "EUR",
"purchase_failed": false,
"happened_at": "2026-07-30T14:02:11+00:00",
"created_at": "2026-07-30T14:07:36+00:00"
}
],
"meta": { "page": 1, "per_page": 1, "total": 2941, "last_page": 2941 }
}event_type_key rides alongside the integer so you can branch on something legible. Nobody reads 11 and thinks "settled".
All three venues write into one history table and every query here is venue-scoped, so CSFloat proceeds never show up under Steam.
Refresh history
/api/v1/market/{venue}/history/syncPull fresh history from the venue.
Requires market.write.
Takes the same optional account_ids as the listings refresh, capped at 1,000.
curl -X POST "https://dashboard.steamlabs.dev/api/v1/market/steam/history/sync" \
-H "Authorization: Bearer $STEAMLABS_API_KEY" \
-H "Content-Type: application/json" \
-d '{}'Steam has its own history task, because its history page is a separate and expensive fetch from the listings one. You get the task_id envelope back.
CSFloat and market.csgo carry history on the same poll as everything else, so this is the same poll-now request the listings refresh makes: { "task_ids": [], "accounts_affected": 34, "sync": "poll_requested" }. Asking for either gets both.
List sale deliveries
Sales in flight on the P2P venues: the item sold, and you still have to hand it over.
/api/v1/market/{venue}/tradesSales awaiting or completing delivery. CSFloat and market.csgo only.
Requires market.read.
On CSFloat and market.csgo the marketplace only brokers the deal. Between "sold" and "paid" there is a Steam trade offer you must send, on a clock, or the venue penalises you. These rows track that window. /market/steam/trades answers 404 venue_unsupported, because Valve holds the item and a Steam sale is finished the moment it happens.
| Parameter | Type | Description |
|---|---|---|
state |
string | A venue state (below), or all, or needs_action for the sales waiting on you right now |
account_id |
uuid | Only sales from this Steam account. Must be one you own |
origin |
string | steamlabs or external |
search |
string | Matches the item name or its market hash name |
sort |
string | A venue sort (below) |
direction |
string | asc or desc (default) |
per_page |
integer | 50 by default, 200 at most |
The state and sort vocabularies are both per venue, because the lifecycles genuinely differ. CSFloat has an accept stage and a 2 hour clock; market.csgo has neither and roughly 7 hours.
| Venue | States | needs_action covers |
Sorts (first is the default) |
|---|---|---|---|
csfloat |
queued, accepted, delivering, delivered, settled, canceled, failed |
queued, accepted |
created_at, sale_price_cents |
marketcsgo |
queued, delivering, delivered, settled, canceled, failed |
queued |
sold_at, created_at, sale_price_cents |
curl "https://dashboard.steamlabs.dev/api/v1/market/csfloat/trades?state=needs_action&per_page=1" \
-H "Authorization: Bearer $STEAMLABS_API_KEY"A CSFloat sale:
{
"data": [
{
"id": "019fb447-2f60-7d13-b8a4-5c0e9d7f2136",
"trade_id": "3f9a1c2e-77bd-4d0a-9f21-8ac4e5b60d11",
"contract_id": "b1c7d0e4-2a55-4f88-9d33-6e2b7a9c1f04",
"marketplace": "csfloat",
"state": "accepted",
"origin": "steamlabs",
"steam_account": {
"id": "019fb42e-9a7e-728d-b960-8b4c2162898c",
"username": "farm_017"
},
"buyer": { "steam64_id": "76561198000000042", "persona": "kessler" },
"asset_id": "38294011745",
"appid": 730,
"game": "cs2",
"market_hash_name": "AK-47 | Redline (Field-Tested)",
"name": "AK-47 | Redline",
"icon_url": "https://csfloat.com/…",
"sale_price_cents": 1980,
"fee_cents": 40,
"receive_cents": 1940,
"currency": "USD",
"steam_offer_id": null,
"steam_offer_state": null,
"delivery_task_id": null,
"delivery_attempts": 0,
"last_error": null,
"accepted_at": "2026-07-30T13:58:02+00:00",
"accept_deadline_at": "2026-07-30T15:58:02+00:00",
"delivered_at": null,
"buyer_accepted_at": null,
"settlement_ends_at": null,
"settled_at": null,
"created_at": "2026-07-30T13:41:19+00:00"
}
],
"meta": { "page": 1, "per_page": 1, "total": 6, "last_page": 6 }
}A market.csgo sale carries market_item_id instead of trade_id and contract_id, no buyer block, and its own clock fields: sold_at, delivery_deadline_at, registered_at, delivered_at, settlement_ends_at, settled_at.
delivery_task_id is the thing to watch. It is set the moment a delivery is queued, and it is why repeating a deliver call finds nothing to do.
Deliver many sales
/api/v1/market/{venue}/trades/deliverSend the items for many due sales, grouped into one task per account.
Requires market.write.
| Field | Type | Description |
|---|---|---|
trade_ids |
uuid[] | The sales to deliver, up to 1,000. Required unless you send filters |
filters |
object | The same filter keys the sales index accepts. Required unless you send trade_ids |
curl -X POST "https://dashboard.steamlabs.dev/api/v1/market/marketcsgo/trades/deliver" \
-H "Authorization: Bearer $STEAMLABS_API_KEY" \
-H "Content-Type: application/json" \
-d '{"filters":{"state":"needs_action"}}'$result = Http::withToken($apiKey)
->post('https://dashboard.steamlabs.dev/api/v1/market/marketcsgo/trades/deliver', [
'filters' => ['state' => 'needs_action'],
])
->json();const response = await fetch('https://dashboard.steamlabs.dev/api/v1/market/marketcsgo/trades/deliver', {
method: 'POST',
headers: {
Authorization: `Bearer ${apiKey}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({ filters: { state: 'needs_action' } }),
});
const result = await response.json();{
"task_ids": [
"019fb448-05aa-7e34-b1f7-2d9c8e0a4b73",
"019fb448-0611-7c02-9a58-4f3b1d7e6055"
],
"accounts_affected": 2,
"sales_affected": 19,
"sales_busy": 0,
"sales_ineligible": 3
}sales_ineligible counts sales that were not deliverable from their current state, and they are skipped rather than failing the batch.
When nothing could be queued you get one of two answers, and they need different responses from you:
| Status | Code | Meaning |
|---|---|---|
409 |
account_busy |
Every selected sale belongs to an account with market work already in flight. Retry shortly |
422 |
nothing_deliverable |
No selected sale was in a deliverable state. Fix the selection |
Both add sales_ineligible and sales_busy.
Cancel many sales
/api/v1/market/{venue}/trades/cancelRefuse many sales before anything moves. CSFloat only.
Requires market.write.
Takes the same trade_ids or filters selection as the deliver endpoint.
curl -X POST "https://dashboard.steamlabs.dev/api/v1/market/csfloat/trades/cancel" \
-H "Authorization: Bearer $STEAMLABS_API_KEY" \
-H "Content-Type: application/json" \
-d '{"trade_ids":["019fb447-2f60-7d13-b8a4-5c0e9d7f2136"]}'Answers 200, not 202. Cancelling is a local state change with no worker involved, so by the time you get this it has already happened:
{
"sales_affected": 1,
"matched": 1
}The window is narrow on purpose: only while the sale is still queued and unassigned. Nothing cancellable in the selection answers 422 nothing_cancelable with matched.
market.csgo offers no cancel at all, here or in the dashboard, and answers 404 venue_unsupported. Its sales are registered with the venue the moment they land, so a local refusal would leave your row saying cancelled while the venue waits for an item and holds you responsible.
Accept and deliver one sale
/api/v1/market/{venue}/trades/{trade}/accept-and-deliverAccept the buyer's offer and send the item. CSFloat only.
Requires market.write.
curl -X POST "https://dashboard.steamlabs.dev/api/v1/market/csfloat/trades/019fb447-2f60-7d13-b8a4-5c0e9d7f2136/accept-and-deliver" \
-H "Authorization: Bearer $STEAMLABS_API_KEY"{
"task_ids": ["019fb449-3b12-70ef-8c47-1a6d5e2f9073"],
"accounts_affected": 1,
"sales_affected": 1
}One endpoint rather than two, on purpose. Accepting without sending starts a 2 hour penalty clock you could not then satisfy from the API.
The sale must be queued with no delivery already assigned, otherwise 422 trade_not_in_state with state and required_state. On market.csgo this answers 404 venue_unsupported: there is no accept stage.
Deliver one sale
/api/v1/market/{venue}/trades/{trade}/deliverSend the item now for a sale that is already due.
Requires market.write.
"Due" means accepted on CSFloat and queued on market.csgo: the same moment in two vocabularies. Either way the sale must have no delivery assigned yet.
curl -X POST "https://dashboard.steamlabs.dev/api/v1/market/marketcsgo/trades/019fb447-9c31-7a80-bd12-70e5c3f4a819/deliver" \
-H "Authorization: Bearer $STEAMLABS_API_KEY"{
"task_ids": ["019fb449-6d70-7188-a933-52c0e7b1f486"],
"accounts_affected": 1,
"sales_affected": 1
}Wrong state gives 422 trade_not_in_state, naming the state it wanted. An account with market work in flight gives 409 account_busy.
Retry a failed delivery
/api/v1/market/{venue}/trades/{trade}/retry-deliveryTry a failed delivery again.
Requires market.write.
curl -X POST "https://dashboard.steamlabs.dev/api/v1/market/csfloat/trades/019fb447-2f60-7d13-b8a4-5c0e9d7f2136/retry-delivery" \
-H "Authorization: Bearer $STEAMLABS_API_KEY"Answers the same 202 envelope as Deliver one sale.
Separate from deliver even though both end in the same task, because the states they start from are disjoint. This one requires failed. Anything else gives 422 trade_not_in_state, which is more useful than one endpoint that silently accepts both.
Check last_error and delivery_attempts on the sale before retrying. A delivery that failed because the item is gone will fail again.
Cancel one sale
/api/v1/market/{venue}/trades/{trade}/cancelRefuse one sale before anything moves. CSFloat only.
Requires market.write.
curl -X POST "https://dashboard.steamlabs.dev/api/v1/market/csfloat/trades/019fb447-2f60-7d13-b8a4-5c0e9d7f2136/cancel" \
-H "Authorization: Bearer $STEAMLABS_API_KEY"Answers 200 with the whole updated sale, in the same shape the index returns:
{
"id": "019fb447-2f60-7d13-b8a4-5c0e9d7f2136",
"trade_id": "3f9a1c2e-77bd-4d0a-9f21-8ac4e5b60d11",
"marketplace": "csfloat",
"state": "canceled",
"sale_price_cents": 1980,
"receive_cents": 1940,
"currency": "USD",
"delivery_task_id": null,
"created_at": "2026-07-30T13:41:19+00:00"
}Only a sale that is still queued and unassigned can be refused. Anything else gives 422 trade_not_in_state. market.csgo answers 404 venue_unsupported.
Errors
Beyond the universal codes, this group returns:
| Status | Code | Meaning |
|---|---|---|
404 |
unknown_venue |
The path segment is not a marketplace. Adds venues |
404 |
venue_unsupported |
The venue has no such surface. Adds venue and supported_venues |
409 |
account_busy |
The account already has marketplace work queued or running. Wait for that task |
422 |
listing_not_open |
The listing is no longer open. Adds state |
422 |
buy_order_not_open |
The buy order is no longer open. Adds state |
422 |
trade_not_in_state |
The sale is in the wrong state. Adds state and required_state |
422 |
nothing_to_cancel |
Nothing in the selection is still open. Adds matched |
422 |
nothing_cancelable |
No selected sale can still be cancelled. Adds matched |
422 |
nothing_deliverable |
No selected sale can be delivered from its state |
422 |
nothing_eligible |
No account matched the refresh, or none could fund the order |
422 |
missing_price_for_currency |
A wallet currency in the selection had no price. Adds missing_currencies and selection_currencies |
422 |
bulk_limit_exceeded |
Over 1,000 ids, or a filter matching over 1,000 rows. Adds max, and matched for a filter |
503 |
maintenance_mode |
Platform maintenance pauses new listings and buy orders. Cancels, syncs and reads keep working. Adds reason |
Everything else, including 401, 403 missing_scope, 403 plan_limit_reached, 422 validation_failed and 429, is on Errors.