Steam Game Data API — Prices, Players & Charts avatar

Steam Game Data API — Prices, Players & Charts

Pricing

from $0.72 / 1,000 game profile returneds

Go to Apify Store
Steam Game Data API — Prices, Players & Charts

Steam Game Data API — Prices, Players & Charts

Scrape Steam as data. One row per game: price, discount, release date, developer, genres, tags, Metacritic, live player count, owner estimates and the review score. Plus the Most Played and Top Sellers charts, Specials, and store search by keyword or tag. No API key, no login.

Pricing

from $0.72 / 1,000 game profile returneds

Rating

0.0

(0)

Developer

Insight Solutions

Insight Solutions

Maintained by Community

Actor stats

0

Bookmarked

2

Total users

1

Monthly active users

9 days ago

Last modified

Share

Steam Game Data API — Prices & Players

Every number Steam publishes about a game, as a flat table. Give this Actor an app ID, a store link or just a game's name — mixed together, in any order — and get one row per game: the current price and discount in any storefront's currency, the release date, developer and publisher, genres and community tags, the Metacritic score, how many people are playing right now, owner estimates, and the review score. Add a chart name and you get Valve's Most Played top 100 or the store's Top Sellers; add a keyword or a tag and you get the store's own search results, 50 to a page.

A Steam game data API, a Steam player count feed and a Steam top sellers scraper in one Actor. No API key. No login. No Steam account. $1.20 per 1,000 games, proxy included, charts and search results are $0.30 per 1,000, games that could not be read are free, and a run that returns nothing costs nothing at all.

Try it in 30 seconds

{
"apps": ["570", "Elden Ring", "https://store.steampowered.com/app/413150/Stardew_Valley/"],
"charts": ["most-played"],
"chartSize": 25,
"country": "us"
}

Three games and the live top 25. A bare ID (570), a store link and the plain name Elden Ring all reach the same place — the name costs one extra request and the row says resolvedFrom: "name" so you can see a guess was made.

Want the market rather than three games? {"charts": ["top-sellers", "specials"], "chartSize": 100}. Want a list? {"searchQueries": ["souls"], "tagIds": ["4026"], "sortBy": "released", "maxResultsPerQuery": 500}.

Output — one row per game, plus one per chart position and per search hit; the fields you will use most are appId, name, price, discountPercent, currentPlayers and reviewScoreDesc (full list under Output reference). Anything that could not be read comes back as a free diagnostic row (ok: false, errorType, error) instead of a charge.

Price — $1.20 per 1,000 games on the FREE tier, $0.30 per 1,000 chart entries and search results, plus $0.001 per run; games that do not exist, searches that matched nothing, blocked requests and every diagnostic row are free, and a run that returns nothing bills nothing at all. Pay-per-event, no API key, no browser, limited permissions — it works over the Apify MCP server (mcp.apify.com) and with agentic (x402) payments.

From code — client.actor("insight.solutions/steam-store-stats-api").call(run_input={…}) with apify-client, or POST https://api.apify.com/v2/acts/insight.solutions~steam-store-stats-api/run-sync-get-dataset-items. Full snippets under Use it from an AI agent, or from code.

What comes back

One game row per app:

{
"ok": true,
"rowType": "game", // "game" | "chart-entry" | "search-result" | "diagnostic"
"appId": "570", // the join key across every row of this game
"name": "Dota 2",
"type": "game", // "game" | "dlc" | "demo" | "music" | "video" | "hardware"
"storeUrl": "https://store.steampowered.com/app/570/",
"resolvedFrom": "app-id", // "app-id" | "name" | "chart" | "search"
"isFree": true,
"price": 0, // in `currency`, after any discount. Free is 0, not null
"initialPrice": 0, // list price before the discount
"discountPercent": 0,
"currency": null, // "USD", "EUR", … — a free game carries no price block
"priceFormatted": null, // "59,99€" — exactly as Steam renders it
"releaseDate": "2013-07-09", // ISO, when Steam gave an actual day
"releaseDateText": "Jul 9, 2013", // Steam's own string: "24 Feb, 2022", "Q2 2027", "To be announced"
"comingSoon": false,
"developers": ["Valve"],
"publishers": ["Valve"],
"genres": ["Action", "Strategy", "Free To Play"],
"categories": ["Multi-player", "Co-op", "Steam Trading Cards", "Steam Workshop"],
"tags": ["Free to Play", "MOBA", "Multiplayer", "Strategy", "e-sports"], // community tags, by votes
"tagVotes": { "Free to Play": 60040, "MOBA": 20225 },
"platforms": ["windows", "mac", "linux"],
"supportedLanguages": ["Bulgarian", "Czech", "English*", "Korean*"], // * = full audio
"metacriticScore": 90,
"achievementCount": 0,
"dlcCount": 2,
"dlcAppIds": ["1241930", "652720"], // feed these back in to price a whole catalogue
"requiredAge": 0,
"controllerSupport": null,
"currentPlayers": 651234, // in the game at this instant, from Valve's own endpoint
"ccuYesterday": 623941, // yesterday's peak, from SteamSpy
"ownersMin": 100000000, // SteamSpy ESTIMATE — a sampled range, not a Valve figure
"ownersMax": 200000000,
"averagePlaytimeMin": 0, // SteamSpy estimate, minutes; 0 where it publishes none
"medianPlaytimeMin": 0,
"reviewScoreDesc": "Very Positive",
"reviewScore": 8, // Steam's internal 0–9 score behind that wording
"totalReviews": 2776617,
"positiveReviews": 2235327,
"negativeReviews": 541290,
"positiveRatio": 80.5,
"country": "us",
"scrapedAt": "2026-09-11T23:05:24.794Z",
"source": "steam-store", // "steam-store" | "steam-api" | "steamspy"
"sourceUrl": "https://store.steampowered.com/app/570/"
}

One chart-entry row per chart position:

{
"ok": true,
"rowType": "chart-entry",
"chart": "most-played",
"chartPeriod": "2026-09-10T00:00:00.000Z", // the month's name on "top-releases"
"rank": 1,
"appId": "730",
"name": "Counter-Strike: Global Offensive",
"peakInGame": 1259987, // Valve's peak for the chart period
"lastWeekRank": 1, // the movement column, free with the chart
"price": 0, "discountPercent": 0, "currency": "USD",
"ownersMin": null, // populated on the SteamSpy chart
"storeUrl": "https://store.steampowered.com/app/730/"
}

And one search-result row per hit:

{
"ok": true,
"rowType": "search-result",
"query": "souls",
"position": 1,
"appId": "677790",
"name": "SOULS",
"itemType": "app", // "app" | "package" | "bundle"
"price": 7.99, "initialPrice": 7.99, "discountPercent": 0, "currency": "USD",
"releaseDateText": "Aug 14, 2017",
"reviewScoreDesc": "Very Positive",
"positiveRatio": 88,
"totalReviews": 124,
"platforms": ["windows"],
"tagIds": [3799, 1742, 7250, 4085], // the same numbers the `tagIds` input takes
"storeUrl": "https://store.steampowered.com/app/677790/SOULS/"
}

Every row carries the same columns, so games, charts, searches and diagnostics export as one rectangular table.

Use cases

  • Price and discount monitoring. A scheduled run over a few hundred app IDs gives you price, initialPrice and discountPercent per storefront, every day, for a fraction of a cent per game. Run the same list with country: "us", "de" and "br" and you have regional pricing in one sheet.
  • Sale detection. {"charts": ["specials"], "chartSize": 100} is one request. Diff it against yesterday and you know what went on sale this morning.
  • Chart tracking. As a Steam top sellers scraper it returns top-sellers and global-top-sellers up to 500 deep; most-played carries rank, peakInGame and lastWeekRank, so a single daily run is already a time series with its own movement column.
  • Market and competitor research. searchQueries with tagIds and sortBy: "released" returns up to 500 rows per query: every game in a genre, with price, review score and release date. That is a market map, not a page of search results.
  • Wishlist and catalogue enrichment. Feed a list of app IDs from anywhere — a wishlist export, a bundle, a spreadsheet — and get names, prices, genres, platforms and review scores back in one pass.
  • Live player-count dashboards. currentPlayers is Valve's own Steam player count for that instant. Poll a list of games hourly and you have concurrency curves without a single scraped web page.
  • Game discovery and LLM pipelines. Name, short description, genres, community tags and review score as a flat array is a labelled corpus for recommendation work — and tagIds on every search row feeds straight back into the next query.

How it works, and why it keeps working

There is no Steam API for this, because Steam's store is not an API — it is a shop with a JSON endpoint behind each of its own widgets. This Actor reads eight of them and merges the results.

SourceWhat it readsShape
Store pagestore.steampowered.com/api/appdetails?appids=…&cc=…JSON. Price, genres, categories, platforms, Metacritic, DLC, languages
Live playersapi.steampowered.com/ISteamUserStats/GetNumberOfCurrentPlayers/v1/JSON. One number: who is in the game right now
Most playedapi.steampowered.com/ISteamChartsService/GetMostPlayedGames/v1/JSON. Valve's live top 100 — ranks, peaks, last week's position
Top releasesapi.steampowered.com/ISteamChartsService/GetTopReleasesPages/v1/JSON. The newest three monthly lists
Store searchstore.steampowered.com/search/results/?…&infinite=1JSON wrapping HTML: 50 result rows a page
Search boxstore.steampowered.com/search/suggest?term=…HTML. Turns a game's name into an app ID
Front pagestore.steampowered.com/api/featuredcategoriesJSON. Specials, new releases, coming soon, top sellers
Review scorestore.steampowered.com/appreviews/<id>?num_per_page=0JSON. The totals, without any of the reviews
SteamSpy (opt-in, on by default)steamspy.com/api.php?request=appdetailsJSON. Owner and playtime estimates, plus community tags

Four things catch naive scrapers here, and all four are handled:

Valve's charts have no names in them. GetMostPlayedGames returns a hundred bare app IDs, and Steam has no batched name lookup (a multi-id appdetails call only accepts the price filter — checked live). So this Actor reuses any names it already learned from SteamSpy's chart in the same run and looks the rest up one appdetails request per app — capped at the chart size you asked for, and with a log line saying how many it resolved. Prices for chart rows are fetched in batches of 100.

Steam's store search is HTML pretending to be JSON. The response is {success, results_html, total_count, start}, where results_html is 110 KB of markup. The parser here reads the attributes off each row — data-ds-appid, data-ds-tagids, data-price-final, the review tooltip — with no DOM library, in about 3 ms a page. It handles the awkward cases the captures contain: free items, discounts with a struck-through original price, rows with no review summary at all (39 of 50 on one captured page), unreleased titles whose "date" is "Q2 2027", and rows where the two price attributes disagree — on the captured Steam Deck listing the wrapper says 39900 and the block that actually renders says 78900.

Prices are per storefront, and the currency is not always stated. appdetails and the front-page sections say what currency they are quoting. Store search does not: it renders "59,99€" and nothing machine-readable. country therefore drives both the request and the currency label, and the row keeps Steam's rendered string in priceFormatted so you can check.

SteamSpy is a volunteer service. It asks for one request a second, and this Actor gives it exactly that — every SteamSpy call in the run queues through a single gate, whatever maxConcurrency is set to. A SteamSpy outage leaves its columns null and is logged; it never fails a row, because a Steam game is worth returning without an owner estimate.

Underneath: Apify datacenter proxy by default, one pinned session per parallel worker. Every endpoint above answered a datacenter address in the captures, which is why residential is not the default and why the proxy cost is already inside the per-row price. When an endpoint refuses an exit IP — HTTP 429, HTTP 403, an empty body, HTML where JSON belongs, or a proxy-side "Access denied" page — that session is retired and the same request goes out once more from a different address. Retrying on an address that was just refused only deepens the block, so it is never done. If the second address is refused too, the job stops, keeps every row it already delivered, and files one free blocked row saying where it stopped. Requests within one job are spaced 250–600 ms apart.

How it compares

  • One schema for games, charts and search. A chart position, a search hit and a game you asked for by ID all produce the same columns, so mixing them is a filter rather than a reconciliation project. appId joins them.
  • Honest about which numbers are estimates. currentPlayers is Valve's. ownersMin/ownersMax are SteamSpy's, they are a range on purpose, and this README says so in three places rather than presenting a midpoint as a fact.
  • Failures are free and legible. A game that does not exist, an entry that was not a Steam app, an empty search or a block produce a diagnostic row with an errorType you can branch on — and no charge. A run that returns nothing at all finishes SUCCEEDED with zero results, a status message that says so, and a free diagnostic row per entry telling you why; it finishes FAILED only when no entry was usable, or the Actor itself hit an error.
  • A partial run is kept, not thrown away. Hit maxRunSecs or your charge ceiling halfway through a 500-row chart and you keep the ranks already written.
  • No login, ever. No Steam account, no cookie jar, no session token, no API key belonging to anyone. Public endpoints only, which is also why it runs under Apify's limited permissions.
  • Reviews are a separate tool, on purpose. This Actor returns the review score; it never fetches individual reviews. For review text, playtime at review, helpful votes and developer responses, use the companion Steam Reviews API — same input formats, same game row, so the two join on appId.

Input reference

FieldTypeDefaultWhat it does
appsarray of stringsprefilled with threeApp IDs, store links or game names. An age-check or community link resolves to the same app. A name costs one extra request and is flagged resolvedFrom: name. DLC, demos and soundtracks work too — the row's type says which. Duplicates are read, and billed, once
chartsarray (multi-select)["most-played"]most-played, top-sellers, global-top-sellers, new-releases, specials, coming-soon, top-releases, steamspy-top-2weeks. The search-backed ones page 50 at a time; specials, new-releases and coming-soon are the store's front-page sections and already carry names and both prices
chartSizeinteger50How deep each chart goes. 100 max for most-played and SteamSpy, 500 for the search-backed charts, and whatever the section holds for the front-page ones
searchQueriesarray of strings[]Store search by keyword. One search-result row per hit, 50 to a page
sortByrelevance | released | name | price-asc | price-desc | reviewsrelevanceThe order the store returns hits in
tagIdsarray of strings[]Restrict searches to Steam tags by numeric ID. Two IDs means both. See the table below
maxResultsPerQueryinteger50Hits per query, up to 500
onlyGamesbooleantrueLeave DLC, soundtracks, software, hardware and bundles out of searches and search-backed charts
countrystringusTwo-letter storefront. Drives price, currency, availability and age rating. Not SteamSpy, which is global
languagestringenSteam's l parameter — the language of descriptions, genres and categories
includePlayerCountbooleantrueAdd currentPlayers. One extra request per game
includeSteamSpybooleantrueAdd owner and playtime estimates and community tags. One extra request per game, rate-limited to one a second run-wide
includeReviewSummarybooleantrueAdd the review score and totals. One extra, very cheap, request per game
enrichChartEntriesbooleanfalseAlso build a full game row for every chart and search hit. Up to four requests per app — an enriched Top 100 is around 400 requests
maxConcurrencyinteger3Games, charts or queries in parallel. Each worker keeps its own proxy session
maxRunSecsinteger240Whole-run wall-clock budget. When it runs out the Actor keeps what it has and files a free diagnostic row for each entry it never reached
proxyConfigurationobjectApify datacenterSteam answers datacenter addresses happily, so the cheap proxy is the default

The 50 Steam tag IDs you will actually use

IDTagIDTagIDTag
19Action1695Open World1667Horror
21Adventure1663FPS1662Survival Horror
122RPG1774Shooter1742Survival
492Indie1625Platformer4295Sandbox
599Simulation1698Puzzle4747Base Building
9Strategy3798Point & Click3871Crafting
597Casual4026Souls-like4166City Builder
128Massively Multiplayer1716Roguelike1654Management
701Sports1773Roguelite1755Turn-Based Strategy
699Racing3839Metroidvania4325Turn-Based Tactics
3859Multiplayer4345Hack and Slash4604Fantasy
4182Singleplayer4085Pixel Graphics3942Sci-fi
1685Co-op41362D1697Anime
3843Online Co-Op4191Story Rich4305Cute
3841Local Co-Op4231Atmospheric4106Relaxing
1775PvP1720Difficult4172Free to Play
3814Team-Based1719Early Access

Every search-result and chart row carries tagIds, so a result can be turned straight into the next query. The full list lives at steamdb.info/tags.

Output reference

ColumnOn which rowsWhat it is
ok, rowTypealltrue plus game / chart-entry / search-result, or false plus diagnostic
input, queryallThe entry this row came from, as you supplied it; the search query on a search row
appIdallSteam's app ID — the join key between every row of one game
name, type, itemTypeallThe game's name; what Steam says it is (game/dlc/demo/…); app, package or bundle
storeUrl, headerImage, websitemostThe store page, the capsule art, the publisher's own site
price, initialPrice, discountPercent, currency, priceFormatted, isFreeall but diagnosticsToday's price, the list price, the discount, and Steam's own rendering of it
releaseDate, releaseDateText, comingSoonall but diagnosticsAn ISO date when Steam gave a real day; Steam's own string either way
developers, publishers, genres, categoriesgameWho made it, who sells it, and Steam's own classifications
tags, tagVotesgameCommunity tags by vote count, from SteamSpy, capped at 20
tagIdschart, searchSteam's numeric tag IDs — the same numbers the tagIds input takes
platforms, supportedLanguages, controllerSupport, requiredAgegameWindows/Mac/Linux, interface languages (* = full audio), controller support, age gate
metacriticScore, metacriticUrl, recommendationsgameThe critic score, its source page, and Steam's own recommendation count
achievementCount, dlcCount, dlcAppIds, fullGameAppIdgameAchievements; the DLC catalogue; on a DLC row, the base game
shortDescription, contentDescriptorsgameThe store blurb, and the publisher's mature-content note
currentPlayersgameHow many people are in the game right now, from Valve
peakInGame, lastWeekRankchartValve's peak for the period, and where the game ranked a week ago
ownersMin, ownersMax, averagePlaytimeMin, medianPlaytimeMin, ccuYesterdaygame, SteamSpy chartSteamSpy estimates. Read the owner figures as one range
reviewScore, reviewScoreDesc, positiveReviews, negativeReviews, totalReviews, positiveRatioall but diagnosticsThe review score. On chart and search rows these come from the store's own row tooltip and are language-filtered
rank, chart, chartPeriodchartPosition, which chart, and when it was computed
positionsearch1-based rank within the query, counting across pages
resolvedFrom, source, sourceUrlallHow the app was identified, which upstream the row came from, and the exact address
error, errorTypediagnosticWhat went wrong, and a type to branch on
country, language, scrapedAtallStorefront, language, and when the row was written

errorType is one of not-found, no-results, blocked, timeout, invalid-input, or unavailable for anything unexpected.

Pricing

$1.20 per 1,000 games. Chart entries and search results are $0.30 per 1,000. Pay-per-event, with proxy already inside those numbers — there is no separate proxy line on your bill for this Actor.

EventWhat triggers itFREEStarterScaleBusiness
Game analysed (primary)One game row — up to four requests' worth of data$0.0012$0.0012$0.00096$0.00072
Chart entry returnedOne position in one Steam chart$0.0003$0.0003$0.0003$0.0003
Search result returnedOne store-search hit$0.0003$0.0003$0.0003$0.0003
Run startedOnce per run, after the first paid row$0.001$0.001$0.001$0.001

Worked example. 100 games with every enrichment on, plus the Most Played top 100:

  • 100 games × $0.0012 = $0.12
  • 100 chart entries × $0.0003 = $0.03
  • 1 run start = $0.001
  • Total: $0.151, from about 403 requests

Price-monitoring example. 500 app IDs, prices only (includePlayerCount, includeSteamSpy and includeReviewSummary all off): 500 × $0.0012 + $0.001 = $0.601 per run, from 500 requests — about $18 a month run daily.

Chart-tracking example. most-played plus specials plus top-sellers at 100 each, no enrichment: 300 × $0.0003 + $0.001 = $0.091 per run, about $2.73 a month run daily, from about a dozen requests.

Market-map example. One tag, sorted by release date, 500 results: 500 × $0.0003 + $0.001 = $0.151, from ten requests.

What you are never charged for: a game that does not exist, an entry that was not a Steam app, a search that matched nothing, a chart position the run never reached before maxRunSecs, or a request the endpoint blocked. A run whose entries were all usable but produced nothing — an app ID that does not exist, a search with no matches, a request the endpoint blocked — finishes SUCCEEDED with zero results, a status message that says so, and a free diagnostic row per entry telling you why; it bills nothing at all, start fee included. A run finishes FAILED only when there was nothing usable to attempt, or the Actor itself hit an error.

Set ACTOR_MAX_TOTAL_CHARGE_USD on a run and the Actor stops fetching once the ceiling is in sight, rather than handing you rows it cannot bill or billing you for rows it cannot hand over. It finishes SUCCEEDED with the ceiling named in its status message, and everything already delivered is yours.

Limits, and the ones that might bite

Owner counts are SteamSpy estimates, not Valve's numbers. Valve has never published owner counts. SteamSpy estimates them by sampling public Steam profiles, and it publishes the result as a band — "100,000,000 .. 200,000,000" — precisely because the middle of that band is not a measurement. This Actor keeps both ends (ownersMin, ownersMax) and never averages them. The same applies to averagePlaytimeMin, medianPlaytimeMin and ccuYesterday: all SteamSpy, all estimates. currentPlayers is the exception — that one is Valve's own live figure. Any tool that gives you a single confident owner number for an arbitrary game is showing you the midpoint of this same range without saying so.

SteamSpy sets the pace of a large run. Its published rate is one request a second and this Actor honours it run-wide, so 1,000 games with includeSteamSpy on is at least 1,000 seconds. Turn it off for big price-monitoring runs; the store data comes back as fast as the proxy allows.

SteamSpy also reports zeros. average_forever and average_2weeks are 0 for every game in the captured top-100 — SteamSpy stopped publishing playtime for many titles when Valve changed what profiles expose. A zero there means "not published", and it is passed through as it arrives rather than converted to null, because SteamSpy really does say 0.

currentPlayers is a snapshot. It is the number at the instant the row was written, not an average or a daily peak. For a peak, ccuYesterday (SteamSpy) and peakInGame (on the most-played chart) are the fields to use. There is no historical player data on any of these endpoints — each run captures the present.

No individual reviews. This Actor returns the review score and the totals. Review text, playtime at review, helpful and funny votes, author profiles and developer responses are the companion Steam Reviews API's job, and it takes the same inputs so the two join on appId.

Prices are per storefront and the storefront is not a translation. country: "de" does not mean "the German price of the US catalogue" — it means the German store, which lists a different set of games, applies different age ratings and prices in EUR. A game that is not sold in a country gets a not-found row for that country, which is correct rather than a bug.

Chart depth is the upstream's, not ours. most-played is exactly 100 entries because that is what Valve publishes. specials, coming-soon and top-sellers from the front page are 10 each and new-releases is 30 — those are the sections' real sizes. Only the search-backed charts go deeper, to 500.

Chart names cost a request each. Valve's most-played and top-releases carry app IDs and nothing else, and Steam offers no batched name lookup, so a 100-row chart is about 100 quick appdetails requests (≈ 30 s). A name that could not be resolved — usually a delisted app — is null and the app ID is still on the row.

Search results are the store's ranking, not a database query. sortBy and tagIds are passed to Steam's own search, and what comes back is what its own front-end would show a shopper — including its relevance model, its regional availability rules and its onlyGames filter. total_count on a busy chart moves between pages; paging past the end returns an empty page rather than an error, and the Actor stops there.

resolvedFrom: "name" means a guess was made. A name is resolved to Steam's first search suggestion. For a distinctive title that is right; for "Souls" it is a coin toss, and pasting the store link removes the ambiguity. The column is there so you can spot it.

The upstream format may change. Steam's store endpoints are the ones its own front-end calls, and none of them is documented or versioned — that is true of every tool that reads this data, including the ones that do not say so. The store-search parser in particular reads an HTML fragment Valve is free to restyle. When a shape changes, rows stop arriving and you get free blocked or no-results diagnostic rows rather than quietly wrong data, and a run that returns nothing bills nothing.

Rate and reliability. Requests go out through proxy sessions pinned per worker, one rotation per block, and a 250–600 ms pause between requests of the same job. Three jobs in parallel is the default; raising maxConcurrency speeds a long list up, and SteamSpy stays at one request a second regardless.

Use it from an AI agent, or from code

One JSON object in, one flat array out — the shape agent runtimes want. The Actor runs with limited permissions, uses pay-per-event pricing and never enters Standby, so it works over the Apify MCP server and with x402 agentic payments. The Integrations tab pushes results to Slack, a webhook, Zapier, Make, Google Sheets, Snowflake or BigQuery.

curl -X POST "https://api.apify.com/v2/acts/insight.solutions~steam-store-stats-api/run-sync-get-dataset-items?token=$APIFY_TOKEN" \
-H "Content-Type: application/json" \
-d '{"apps":["570","1245620"],"charts":["most-played"],"chartSize":25}'
# pip install apify-client
from apify_client import ApifyClient
client = ApifyClient("<APIFY_TOKEN>")
run = client.actor("insight.solutions/steam-store-stats-api").call(run_input={
"apps": ["Elden Ring", "570"],
"charts": ["top-sellers"],
"chartSize": 100,
"country": "de",
})
for row in client.dataset(run["defaultDatasetId"]).iterate_items():
if row["rowType"] == "game":
print(row["name"], row["price"], row["currency"], row["currentPlayers"])

FAQ

Do I need a Steam account or an API key? No. Every source here is public: the store's own JSON endpoints, Valve's public Web API charts, and SteamSpy. The Actor never logs in and never accepts a cookie or session token.

How do I deduplicate between runs? On appId. An incremental price history is an append keyed on (appId, country, scrapedAt); a catalogue is an upsert on (appId, country).

Can I get a game's price in several currencies at once? Run it once per country. Each run's rows carry their own country and currency, so they stack into one table.

Why is currency null on a free game? Because Steam sends no price block at all for one. price is 0 and isFree is true, which together say everything the missing currency would have.

Why did a chart row come back with no name? Valve's most-played and top-releases charts contain app IDs only. The Actor resolves names from SteamSpy's chart when it is in the same run and otherwise one appdetails request per app; if that fails for an app — usually one that has been delisted — the row keeps the rank and the ID.

Why is totalReviews smaller on a search row than on the game row? They come from different places. The game row's figure is every review in every language, from the review endpoint. The search row's is the count Steam prints in its own tooltip, which is filtered to the languages the storefront serves.

What does enrichChartEntries cost? Up to four requests per app, and each resulting row is billed as a game. An enriched Top 100 is about 400 requests and $0.15. The chart rows alone are $0.03. It is off by default for that reason.

Can I search without a keyword? Yes — leave searchQueries with an empty string or use the top-sellers chart, and set tagIds. That browses a tag rather than searching for words.

What happens if one game fails? The others still run. The failed one produces a free diagnostic row and the run finishes SUCCEEDED. If every entry fails, the run still finishes SUCCEEDED, with zero results, a status message that says so and one diagnostic row per entry, and you are billed nothing at all. It finishes FAILED only when nothing you sent could be read as an app, a chart name or a search query, or the Actor itself hit an error.

Is the data fresh? Live. Every run reads Steam at that moment; nothing is cached. SteamSpy's own figures are as fresh as SteamSpy's last sample, which is its business, not ours.

  • Public endpoints only. Every address this Actor reads is one Steam's own store front-end calls, unauthenticated, from a browser. It never logs in, never accepts cookies or session tokens, and never uses an API key belonging to anyone.
  • No personal data. This Actor returns facts about products: prices, genres, player counts, aggregate review scores. It reads no user profiles, no reviews, no author names and no Steam IDs. The companion reviews Actor handles review authors and has its own notes on that.
  • SteamSpy's numbers are a third party's estimates. Owner and playtime figures come from SteamSpy, not from Valve, and they are estimates derived from sampled public profiles. Attribute them accordingly if you republish them, and do not present the middle of an owner band as a measurement.
  • Store content belongs to its publisher. Names, descriptions, capsule art and screenshots are copyrighted works. Indexing, price comparison, analysis and linking are the ordinary uses and are what this is built for; republishing artwork or descriptions is your call and your responsibility.
  • Not affiliated with Valve Corporation, with SteamSpy, or with any game, developer or publisher whose data you retrieve. Steam is a trademark of Valve Corporation. All names and trademarks belong to their owners and are used only to describe which public endpoints this Actor reads.

Our other Actors

Every Insight Solutions Actor is pay-per-result with no browser, no login and no API key, and every one of them returns free diagnostic rows instead of billing for failures. Prices are per 1,000 results.

Video, audio & social

News, documents & the web

Business, finance & jobs

Apps & games