BUFF sale history is now available - POST /v1/archive/buff returns sale prices and daily total supply since September 2024.

How to Build a CS2 Skin Price Alert Bot

Updated

Endpoints used: POST /v1/prices/latest & POST /v1/prices/history

A CS2 price alert bot should poll cs2.sh's POST /v1/prices/latest for a bounded watchlist, evaluate one explicit field such as a marketplace ask or bid, and notify only when the value crosses a stored threshold with acceptable freshness. Persist the last accepted observation and alert state so restarts, null data, and temporary API failures do not create duplicate messages.

Scope: This is a broad, high-level v0 guide, not a complete application. cs2.sh is designed as a flexible market-data layer for whatever you want to build on top of it, so adapt the implementation to your own product, strategy, and integrations.

The delivery channel can be Discord, Telegram, email, push, or a webhook. The market-data loop stays the same because notification delivery is separate from deciding whether a price event occurred.

Define alerts precisely#

Every rule needs enough information to answer one question without guessing:

text
market_hash_name
variant_version, if any
source
field: ask or bid
condition: below, above, or percentage move
threshold
freshness_limit
cooldown

An ask-below alert means a sell listing is available at or below the target. A bid-above alert means a buy order is available at or above the target. Do not label a bid as a sale price or fire an ask alert from a completed-sale series.

Source is part of the rule. A user asking for a CSFloat listing below $100 should not be alerted because BUFF crossed $100. A separate "any cash marketplace" rule can evaluate all eligible sources and include the winning source in the notification.

Poll the watchlist in batches#

Deduplicate subscribed names, split them into batches of at most 100, and send them to POST /v1/prices/latest. One returned item can satisfy many user subscriptions, so fetch once per polling cycle and evaluate all matching rules locally.

For each requested item:

  1. Select the base item or exact nested variant.
  2. Read the configured source and field.
  3. Reject null or missing values. A missing price is not zero.
  4. Reject the observation when collected_at is older than the rule's tolerance. If the marketplace's own update time matters, check updated_at too.
  5. Compare the accepted value with the previous accepted value and the threshold.
  6. Commit state and enqueue the notification atomically so a worker retry cannot send the same crossing twice.

POST endpoints can return successful items and per-item errors[] together. Record failures for only the affected names and continue evaluating successful items. A malformed name should disable or flag that subscription; it should not fail the whole watchlist.

Alert on crossings, not every matching poll#

For a below-target rule, send when the state changes from above the target to at or below it:

text
crossed_below = previous_price > target and current_price <= target

Keep the rule in a triggered state until the price rises above a re-arm level. A small hysteresis band prevents a price moving between $99.99 and $100.01 from generating a message every cycle.

Cooldown controls delivery frequency, while state controls correctness. A cooldown by itself can resend the same unchanged condition after time expires. Store a deterministic event key such as rule id, source collected_at, field, and observed value to make notification retries idempotent.

Percentage-move alerts need a real baseline#

Define both the window and the price field. "Down 10%" could mean the current ask versus the last accepted ask, the close ask one hour ago, or a daily completed-sale price. Those are different alerts.

For fixed historical windows, POST /v1/prices/history returns ask and bid OHLC at 5m, 30m, 1h, and 1d intervals. Compare the current ask with a prior ask close from the same source. Use close_time to confirm the baseline observation actually occurred near the end of its bucket, and retain sample_count so a poorly observed bucket can be excluded.

OHLC history begins December 24, 2025. The maximum range per request is 14 days at 5m, 90 days at 30m, 365 days at 1h, and unlimited at 1d.

Variants and special attributes#

Variant objects are sparse. A Phase 2 rule should remain unknown when the configured source has no Phase 2 object; it must not fall back to the base Doppler. Steam cannot drive phase-specific alerts because Steam does not distinguish those variants.

The current-price endpoint does not identify individual asset listings by float, paint seed, or stickers. For an exact-asset alert, use a marketplace listing interface that exposes those assets and use cs2.sh as the reference-price layer.

Failure behavior#

  • On 429, delay and retry within the documented request rate rather than increasing concurrency.
  • On temporary 5xx, retain the last accepted observation but do not evaluate it as new.
  • On 401 or 403, stop the worker and fix the key or plan. Repeated polling cannot correct authorization.
  • On a stale source, show the rule as delayed instead of sending a price alert.
  • On delivery failure, retry the existing notification event. Do not recalculate the market crossing during the delivery retry.

The result is a small state machine: fetch, validate, compare, persist, and deliver. Keeping those stages separate makes it possible to prove why each message was sent.

References: current prices, price history, API errors and limits, and data coverage.