GET /v1/listings/stream — Live Feed of New and Sold Listings
Subscribe to CS2 listings the moment they appear and the moment they sell, over Server-Sent Events. Replaces polling, and tells you when items disappear.
curl --request GET \
--url https://csboard.com/v1/listings/stream \
--header 'Authorization: Bearer <token>'import requests
url = "https://csboard.com/v1/listings/stream"
headers = {"Authorization": "Bearer <token>"}
response = requests.get(url, headers=headers)
print(response.text)const options = {method: 'GET', headers: {Authorization: 'Bearer <token>'}};
fetch('https://csboard.com/v1/listings/stream', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://csboard.com/v1/listings/stream",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "GET",
CURLOPT_HTTPHEADER => [
"Authorization: Bearer <token>"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"net/http"
"io"
)
func main() {
url := "https://csboard.com/v1/listings/stream"
req, _ := http.NewRequest("GET", url, nil)
req.Header.Add("Authorization", "Bearer <token>")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.get("https://csboard.com/v1/listings/stream")
.header("Authorization", "Bearer <token>")
.asString();require 'uri'
require 'net/http'
url = URI("https://csboard.com/v1/listings/stream")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Get.new(url)
request["Authorization"] = 'Bearer <token>'
response = http.request(request)
puts response.read_body<string>{
"code": "<string>",
"detail": "<string>"
}{
"code": "<string>",
"detail": "<string>"
}Authorization: Bearer csb_pub_....
Query parameters
Filters are applied on our side, before anything is sent to you. They exist to save your bandwidth — one stream already carries the whole catalogue, so you never need several connections to cover different segments.Rifle, Knife, Gloves.Classified, Covert.Factory New, Minimal Wear, Field-Tested, Well-Worn, Battle-Scarred.market_hash_name (case-insensitive) — stream one specific item.only or exclude.only or exclude.instant or hold. Narrows new events to one delivery bucket — a withdrawal bot subscribing with delivery=instant never sees an item it cannot ship today.gone events are never filtered: you may be holding the item from before your filter existed, and a missed removal is the ghost listing this feed exists to prevent.Last-Event-ID header — the header is the standard mechanism and is preferred.Events
Listing object GET /v1/listings returns, including listed_at.This fires for genuinely new inventory and for re-listings — items returning from cancelled orders, expired trade offers, or released holds. A re-listed item is new to you even though it existed before.{ "id": "..." }.Act on this one. Dropping items as they sell is the difference between your buy calls succeeding and your buy calls discovering the item was already gone. This is the half of the picture polling cannot give you at any frequency.Last-Event-ID is older than the retained history, so a replay would be incomplete. Payload is { "reason": "last_event_id_expired", "detail": "..." }.Re-read GET /v1/listings?sort=newest to rebuild your view, then reconnect without Last-Event-ID. We send this rather than silently handing you a partial replay you would mistake for a complete one.Reconnecting without gaps
Every event carries anid. Send the last id you processed back as the Last-Event-ID header on reconnect and you receive exactly what you missed — including across our deploys, which drop open connections by design.
A : heartbeat comment arrives every 25 seconds. Treat a longer silence as a dead connection and reconnect; browsers’ EventSource does this and resends Last-Event-ID for you.
Limits
Three concurrent streams per API key. The connection itself is not rate-limited — once open, events flow as fast as the catalogue changes.Example request
curl -N "https://csboard.com/v1/listings/stream?max_price=250" \
-H "Authorization: Bearer csb_pub_..." \
-H "Accept: text/event-stream"
Example stream
: connected
id: 1785312840123-0
event: new
data: {"id":"000012e8-1f4a-4c2b-9d3e-7a1b2c3d4e5f","market_hash_name":"AK-47 | Redline (Field-Tested)","wear":"Field-Tested","float_value":0.2418,"price_usd":31.2,"category":"Rifle","rarity":"Classified","tradable":true,"delivery":"instant","listed_at":"2026-07-27T09:14:02.481Z"}
id: 1785312851904-0
event: gone
data: {"id":"000012e8-1f4a-4c2b-9d3e-7a1b2c3d4e5f"}
: heartbeat
Example client
const es = new EventSource("https://csboard.com/v1/listings/stream?max_price=250");
const live = new Map();
es.addEventListener("new", (e) => {
const listing = JSON.parse(e.data);
live.set(listing.id, listing);
});
es.addEventListener("gone", (e) => {
live.delete(JSON.parse(e.data).id);
});
es.addEventListener("resync", () => {
live.clear();
// re-read /v1/listings?sort=newest, then let EventSource reconnect
});
EventSource cannot set an Authorization header — in a server-side runtime use an SSE client that can (or pass the key however your HTTP client allows). The snippet above shows the event handling, not the auth.
Error codes
| HTTP status | Code | Meaning |
|---|---|---|
| 401 | missing_api_key, invalid_api_key | Missing or invalid API key. |
| 429 | too_many_streams | More than 3 concurrent streams for this key. Close an existing connection first. |
/v1/listings on a timer at all — keep it for your initial catalogue load and for recovering after a resync. If you cannot hold a long-lived connection, the next best thing is GET /v1/listings with available_after, which turns each poll into a delta read.Authorizations
Send your key as a Bearer token on every request: Authorization: Bearer csb_pub_.... Generate keys in your CSBoard profile.
Query Parameters
Only stream listings at or above this USD price.
Only stream listings at or below this USD price.
e.g. Rifle, Knife, Gloves.
e.g. Classified, Covert.
Exact wear name.
Factory New, Minimal Wear, Field-Tested, Well-Worn, Battle-Scarred Exact market_hash_name (case-insensitive).
Filter StatTrak™ items.
only, exclude Filter Souvenir items.
only, exclude Narrow new events to one delivery bucket (instant or hold). gone events are never filtered — a missed removal is the ghost listing this feed exists to prevent.
instant, up_to_12h, hold Resume after this event id. Use only if your client cannot send the Last-Event-ID header.
Narrow new events to listings whose refund_percent is at least this (0-100). gone events are never filtered.
0 <= x <= 100Response
An open SSE stream. Stays open until you disconnect.
The response is of type string.
curl --request GET \
--url https://csboard.com/v1/listings/stream \
--header 'Authorization: Bearer <token>'import requests
url = "https://csboard.com/v1/listings/stream"
headers = {"Authorization": "Bearer <token>"}
response = requests.get(url, headers=headers)
print(response.text)const options = {method: 'GET', headers: {Authorization: 'Bearer <token>'}};
fetch('https://csboard.com/v1/listings/stream', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://csboard.com/v1/listings/stream",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "GET",
CURLOPT_HTTPHEADER => [
"Authorization: Bearer <token>"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"net/http"
"io"
)
func main() {
url := "https://csboard.com/v1/listings/stream"
req, _ := http.NewRequest("GET", url, nil)
req.Header.Add("Authorization", "Bearer <token>")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.get("https://csboard.com/v1/listings/stream")
.header("Authorization", "Bearer <token>")
.asString();require 'uri'
require 'net/http'
url = URI("https://csboard.com/v1/listings/stream")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Get.new(url)
request["Authorization"] = 'Bearer <token>'
response = http.request(request)
puts response.read_body<string>{
"code": "<string>",
"detail": "<string>"
}{
"code": "<string>",
"detail": "<string>"
}