Market Data
GET /v1/listings/snapshot.ndjson.gz — Every Listing in One File
Download every buyable CSBoard listing as one gzipped NDJSON file, then keep it current from the SSE stream. The replacement for paging the whole catalogue.
GET
/
listings
/
snapshot.ndjson.gz
Every buyable listing in one file (gzipped NDJSON)
curl --request GET \
--url https://csboard.com/v1/listings/snapshot.ndjson.gz \
--header 'Authorization: Bearer <token>'import requests
url = "https://csboard.com/v1/listings/snapshot.ndjson.gz"
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/snapshot.ndjson.gz', 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/snapshot.ndjson.gz",
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/snapshot.ndjson.gz"
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/snapshot.ndjson.gz")
.header("Authorization", "Bearer <token>")
.asString();require 'uri'
require 'net/http'
url = URI("https://csboard.com/v1/listings/snapshot.ndjson.gz")
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": "missing_api_key",
"detail": "Provide your API key via `Authorization: Bearer csb_pub_…`. Generate one at https://csboard.com/profile?tab=api"
}Every buyable listing as one gzipped NDJSON file. Each decompressed line is one
The file is not filtered: all three
Listing JSON object — the exact shape GET /v1/listings returns, with the same one-listing-per-physical-skin rule and your negotiated discount already applied.
This is the way to hold a complete local copy of the shelf. Walking /v1/listings page by page works for a filtered subset; for everything, it is tens of thousands of reads per pass. Download this file once instead, then open /v1/listings/stream from the position the file hands you, and your copy stays current from there.
Authentication required. Send your key as Authorization: Bearer csb_pub_....
Limit: six full downloads per hour per key. A 304 answer to a conditional request is free.
The pairing: file + stream
- Download the file. Keep the
ETagand theX-Stream-Positionresponse headers. - Connect to
/v1/listings/stream?last_event_id=<X-Stream-Position>. The stream replays everything that happened since the file was built, then goes live. Applynewas an upsert byid(a listing may already be in your copy) andgoneas a delete. - Re-download on your own cadence — hourly is plenty — with
If-None-Match. A reprice of a listing that was already live is not a stream event; the next file carries it. Between files a stale price is refused at checkout withprice_moved, never filled.
resync, your position has aged out of its history: download the file again and reconnect from its new X-Stream-Position.
Request headers
string
The
ETag from your previous download. Unchanged → 304 Not Modified, no body, no charge against the hourly limit.string
The
Last-Modified from your previous download. Same effect as If-None-Match.Response headers
| Header | Description |
|---|---|
ETag | Opaque version of this file. Send it back as If-None-Match. |
Last-Modified | When this file was built (HTTP date). |
X-Snapshot-At | The same build time, ISO-8601 with milliseconds. |
X-Stream-Position | The stream event id to connect with (last_event_id) so nothing between this file and your live view is missed. |
X-Row-Count | Number of listings in the file. |
Content-Encoding | gzip — the body is always compressed; Content-Length is the compressed size. |
Response body
A gzip stream. Every decompressed line is aListing:
{"id":"000012e8-1f4a-4c2b-9d3e-7a1b2c3d4e5f","market_hash_name":"AK-47 | Redline (Field-Tested)","wear":"Field-Tested","doppler_phase":null,"float_value":0.2613,"paint_seed":661,"stickers":[{"name":"Sticker | Natus Vincere | Katowice 2015","image":"https://…","slot":2,"wear":0.12}],"price_usd":31.2,"category":"Rifle","rarity":"Classified","image":"https://…","inspect_link":"steam://rungame/730/…","tradable":true,"tradable_at":null,"delivery":"instant","refund_percent":100,"listed_at":"2026-10-20T09:14:02.481Z"}
delivery buckets (instant, up_to_12h, hold) are in it. Filter on the field locally. It is rebuilt every 10 minutes while anyone is downloading it, CS2 only (appId is not read).
Example: download, then stream from the file’s position
curl https://csboard.com/v1/listings/snapshot.ndjson.gz \
-H "Authorization: Bearer csb_pub_..." \
--dump-header headers.txt \
--output listings.ndjson.gz
POS=$(grep -i '^x-stream-position' headers.txt | awk '{print $2}' | tr -d '\r')
curl -N "https://csboard.com/v1/listings/stream?last_event_id=$POS" \
-H "Authorization: Bearer csb_pub_..." \
-H "Accept: text/event-stream"
Example: conditional re-download
curl https://csboard.com/v1/listings/snapshot.ndjson.gz \
-H "Authorization: Bearer csb_pub_..." \
-H 'If-None-Match: "lsnap-1792000000000-3900000"' \
--output listings.ndjson.gz
304 means your file is the current one.
Responses other than 200
| HTTP status | Code | Meaning |
|---|---|---|
| 202 | snapshot_building | No file exists yet — the first minutes after a deploy. Retry after Retry-After seconds (also in retry_after_seconds). |
| 304 | (no body) | Unchanged since the ETag or date you sent. |
| 401 | missing_api_key, invalid_api_key | Missing or invalid API key. |
| 429 | snapshot_rate_limited | More than six full downloads this hour. Send If-None-Match and take live changes from the stream. |
The price list by name — one row per
market_hash_name with its cheapest ask — is a different, much smaller file: GET /v1/prices/snapshot.ndjson.gz.Authorizations
Send your key as a Bearer token on every request: Authorization: Bearer csb_pub_.... Generate keys in your CSBoard profile.
Headers
Conditional request. Pass the ETag from a previous download to receive 304 if unchanged.
Conditional request by the previous Last-Modified value.
Response
Gzipped NDJSON. Each decompressed line is one Listing JSON object.
The response is of type file.
⌘I
Every buyable listing in one file (gzipped NDJSON)
curl --request GET \
--url https://csboard.com/v1/listings/snapshot.ndjson.gz \
--header 'Authorization: Bearer <token>'import requests
url = "https://csboard.com/v1/listings/snapshot.ndjson.gz"
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/snapshot.ndjson.gz', 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/snapshot.ndjson.gz",
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/snapshot.ndjson.gz"
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/snapshot.ndjson.gz")
.header("Authorization", "Bearer <token>")
.asString();require 'uri'
require 'net/http'
url = URI("https://csboard.com/v1/listings/snapshot.ndjson.gz")
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": "missing_api_key",
"detail": "Provide your API key via `Authorization: Bearer csb_pub_…`. Generate one at https://csboard.com/profile?tab=api"
}