> ## Documentation Index
> Fetch the complete documentation index at: https://api.csboard.com/llms.txt
> Use this file to discover all available pages before exploring further.

# GET /v1/listings/stream — живая лента появления и продажи листингов

> Подписка на листинги CS2 в момент их появления и в момент продажи, через Server-Sent Events. Заменяет опрос и сообщает, когда предметы исчезают.

Лента изменений каталога через Server-Sent Events. Одно соединение заменяет любой цикл опроса.

Опрос не может дёшево ответить на вопрос *«что нового?»* — любой клиент, который пытается, в итоге раз за разом перечитывает одну и ту же первую страницу, и ответ почти всегда «ничего». А на вопрос *«что исчезло?»* опрос не отвечает вообще: поллер узнаёт о продаже предмета, только попытавшись его купить и получив отказ. Этот эндпоинт присылает и то, и другое.

**Требуется аутентификация.** Отправьте ключ как `Authorization: Bearer csb_pub_...`.

## Query-параметры

Фильтры применяются на нашей стороне, до отправки вам. Они нужны для экономии вашего трафика — одно соединение уже несёт весь каталог, поэтому несколько подключений под разные сегменты не нужны.

<ParamField query="min_price" type="number">
  Присылать только листинги с ценой не ниже указанной (USD).
</ParamField>

<ParamField query="max_price" type="number">
  Присылать только листинги с ценой не выше указанной (USD).
</ParamField>

<ParamField query="category" type="string">
  Только эта категория, например `Rifle`, `Knife`, `Gloves`.
</ParamField>

<ParamField query="rarity" type="string">
  Только этот уровень редкости, например `Classified`, `Covert`.
</ParamField>

<ParamField query="wear" type="string">
  Только этот диапазон износа: `Factory New`, `Minimal Wear`, `Field-Tested`, `Well-Worn`, `Battle-Scarred`.
</ParamField>

<ParamField query="name" type="string">
  Точное `market_hash_name` (регистр не учитывается) — поток по одному конкретному предмету.
</ParamField>

<ParamField query="stat_trak" type="string">
  Фильтр StatTrak™. `only` или `exclude`.
</ParamField>

<ParamField query="souvenir" type="string">
  Фильтр Souvenir. `only` или `exclude`.
</ParamField>

<ParamField query="last_event_id" type="string">
  Продолжить после этого id события. Используйте, только если ваш клиент не умеет отправлять заголовок `Last-Event-ID` — заголовок является стандартным механизмом и предпочтителен.
</ParamField>

## События

<ResponseField name="new" type="Listing">
  Листинг появился в каталоге. Полезная нагрузка — ровно тот объект `Listing`, который возвращает `GET /v1/listings`, включая `listed_at`.

  Срабатывает как для действительно нового инвентаря, **так и** для перевыставлений — предметов, вернувшихся из отменённых заказов, истёкших трейд-офферов или снятых холдов. Перевыставленный предмет для вас новый, даже если существовал раньше.
</ResponseField>

<ResponseField name="gone" type="object">
  Листинг покинул каталог — продан, зарезервирован или снят. Полезная нагрузка — `{ "id": "..." }`.

  **Именно на это событие нужно реагировать.** Убирать предметы в момент продажи — это разница между тем, что ваши запросы на покупку проходят, и тем, что они узнают о пропаже предмета постфактум. Эту половину картины опрос не даёт ни на какой частоте.
</ResponseField>

<ResponseField name="resync" type="object">
  Ваш `Last-Event-ID` старше сохранённой истории, поэтому повтор был бы неполным. Полезная нагрузка — `{ "reason": "last_event_id_expired", "detail": "..." }`.

  Перечитайте `GET /v1/listings?sort=newest`, чтобы восстановить своё состояние, затем переподключитесь без `Last-Event-ID`. Мы присылаем это событие вместо того, чтобы молча отдать вам частичный повтор, который вы приняли бы за полный.
</ResponseField>

## Переподключение без пропусков

У каждого события есть `id`. Отправьте последний обработанный id обратно в заголовке `Last-Event-ID` при переподключении — и получите ровно то, что пропустили, включая наши деплои, которые по своей природе рвут открытые соединения.

Каждые 25 секунд приходит комментарий `: heartbeat`. Более долгую тишину считайте мёртвым соединением и переподключайтесь; браузерный `EventSource` делает это сам и сам же отправляет `Last-Event-ID`.

## Лимиты

Три одновременных потока на один API-ключ. Само соединение не ограничено по частоте — события идут с той скоростью, с какой меняется каталог.

## Пример запроса

```bash theme={null}
curl -N "https://csboard.com/v1/listings/stream?max_price=250" \
  -H "Authorization: Bearer csb_pub_..." \
  -H "Accept: text/event-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
```

## Пример клиента

```javascript theme={null}
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();
  // перечитайте /v1/listings?sort=newest, затем дайте EventSource переподключиться
});
```

`EventSource` не умеет выставлять заголовок `Authorization` — в серверной среде используйте SSE-клиент, который это умеет (или передайте ключ так, как позволяет ваш HTTP-клиент). Сниппет выше показывает обработку событий, а не аутентификацию.

## Коды ошибок

| HTTP-статус | Код                | Значение                                                                  |
| ----------- | ------------------ | ------------------------------------------------------------------------- |
| 401         | `unauthorized`     | Отсутствует или некорректный API-ключ.                                    |
| 429         | `too_many_streams` | Больше 3 одновременных потоков на ключ. Закройте существующее соединение. |

<Tip>
  Если вы используете стрим, опрашивать `/v1/listings` по таймеру не нужно вообще — оставьте его для первичной загрузки каталога и для восстановления после `resync`. Если удерживать долгоживущее соединение вы не можете, следующий по эффективности вариант — `GET /v1/listings` с параметром `available_after`, который превращает каждый опрос в чтение дельты.
</Tip>


## OpenAPI

````yaml GET /listings/stream
openapi: 3.1.0
info:
  title: CSBoard API
  version: 1.0.0
  description: >-
    Market data over the CSBoard marketplace — live listings, floats, stickers,
    minAsk prices, FX rates — plus opt-in buying straight from your balance.
    Free to read, key-gated, built for automation.
  contact:
    name: CSBoard
    url: https://csboard.com/docs
servers:
  - url: https://csboard.com/v1
    description: Production
security:
  - bearerAuth: []
tags:
  - name: Status
    description: Liveness and freshness probes.
  - name: Market data
    description: Read the live catalog, prices, and FX rates.
  - name: Trading
    description: Buy listings from your CSBoard balance. Opt-in, key-gated.
  - name: Account
    description: Your balance, settled funds, and trading status.
  - name: Webhooks
    description: Register an endpoint and receive signed order updates instead of polling.
paths:
  /listings/stream:
    get:
      tags:
        - Market data
      summary: Stream listings as they appear and disappear
      description: >-
        Server-Sent Events feed of the catalogue changing. One connection
        replaces any polling loop.


        Polling cannot answer "what is new?" cheaply — every client that tries
        ends up re-reading the same first page on a timer. It also cannot answer
        "what is gone?" at all: a poller only learns an item sold by trying to
        buy it and failing. This endpoint pushes both edges.


        ### Events


        - `new` — data is exactly the `Listing` object `/v1/listings` returns.

        - `gone` — data is `{ "id": "…" }`. The item left the catalogue. **Act
        on this.** Dropping items as they sell is the difference between your
        buy calls succeeding and your buy calls discovering the item was already
        gone.

        - `resync` — your `Last-Event-ID` predates the retained history, so a
        replay would be incomplete. Re-read `/v1/listings?sort=newest` before
        trusting the stream again.


        ### Reconnecting without gaps


        Every event carries an id. Send the last one you processed back as the
        `Last-Event-ID` header and you receive exactly what you missed —
        including across our deploys. A `: heartbeat` comment arrives every 25
        seconds; treat a longer silence as a dead connection and reconnect.


        ### Limits


        Three concurrent streams per key. One stream already carries the whole
        catalogue, so the filters below are for your bandwidth, not for working
        around that.


        ```

        : connected


        id: 1785312840123-0

        event: new

        data: {"id":"000012e8-…","market_hash_name":"AK-47 | Redline
        (Field-Tested)","price_usd":31.2,"delivery":"instant","listed_at":"2026-07-27T09:14:02.481Z"}


        id: 1785312851904-0

        event: gone

        data: {"id":"000012e8-…"}


        : heartbeat

        ```
      operationId: streamListings
      parameters:
        - name: min_price
          in: query
          required: false
          description: Only stream listings at or above this USD price.
          schema:
            type: number
        - name: max_price
          in: query
          required: false
          description: Only stream listings at or below this USD price.
          schema:
            type: number
        - name: category
          in: query
          required: false
          description: e.g. Rifle, Knife, Gloves.
          schema:
            type: string
        - name: rarity
          in: query
          required: false
          description: e.g. Classified, Covert.
          schema:
            type: string
        - name: wear
          in: query
          required: false
          description: Exact wear name.
          schema:
            type: string
            enum:
              - Factory New
              - Minimal Wear
              - Field-Tested
              - Well-Worn
              - Battle-Scarred
        - name: name
          in: query
          required: false
          description: Exact market_hash_name (case-insensitive).
          schema:
            type: string
        - name: stat_trak
          in: query
          required: false
          description: Filter StatTrak™ items.
          schema:
            type: string
            enum:
              - only
              - exclude
        - name: souvenir
          in: query
          required: false
          description: Filter Souvenir items.
          schema:
            type: string
            enum:
              - only
              - exclude
        - name: delivery
          in: query
          description: >-
            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.
          schema:
            type: string
            enum:
              - instant
              - up_to_12h
              - hold
        - name: last_event_id
          in: query
          required: false
          description: >-
            Resume after this event id. Use only if your client cannot send the
            `Last-Event-ID` header.
          schema:
            type: string
          example: 1785312840123-0
        - name: min_refund_percent
          in: query
          description: >-
            Narrow `new` events to listings whose refund_percent is at least
            this (0-100). `gone` events are never filtered.
          schema:
            type: number
            minimum: 0
            maximum: 100
      responses:
        '200':
          description: An open SSE stream. Stays open until you disconnect.
          content:
            text/event-stream:
              schema:
                type: string
        '401':
          description: Missing or invalid API key.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '429':
          description: Too many concurrent streams for this key (max 3).
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
components:
  schemas:
    Error:
      type: object
      description: >-
        All errors return { code, detail }. Some carry extra fields (e.g.
        price_moved adds current_total_usd, insufficient_balance adds
        required_usd/current_usd).
      properties:
        code:
          type: string
          description: >-
            Machine-readable error code, e.g. rate_limit_exceeded,
            trading_not_enabled, price_moved.
        detail:
          type: string
          description: Human-readable explanation.
      required:
        - code
  securitySchemes:
    bearerAuth:
      type: http
      scheme: bearer
      description: >-
        Send your key as a Bearer token on every request: `Authorization: Bearer
        csb_pub_...`. Generate keys in your CSBoard profile.

````