Quick answer: Steam publishes regional prices on the public
store.steampowered.com/api/appdetailsendpoint — but it returns one currency at a time, tied to your request IP. Building a 60-region price matrix means 60 requests with 60 exit IPs, handling Steam's rate limit (~200 req/5 min), and normalizing currencies into a USD equivalent without a third-party FX feed. The Apify Actor below does all of that in one run for $0.001 per row (~$1.05 per 1,000 results), returning Pydantic-validated flat JSON with one row per(appid × country_code)pair.
Steam quietly runs the largest regional-pricing matrix in consumer software. The same copy of Cyberpunk 2077 lists for $59.99 in the US, ~€49.99 in Germany, and ARS 4,249.99 in Argentina — not rounding differences, but Valve's explicit regional price tiers, updated periodically in response to purchasing-power parity and local competition. For indie devs auditing pricing, analysts studying platform economics, or journalists writing "cheapest Steam region in 2026" pieces, that matrix is exactly the dataset you need. The catch is assembling it.
What is the Steam Store regional pricing API? 🎮
The Steam Store exposes an undocumented but widely used REST endpoint: GET https://store.steampowered.com/api/appdetails?appids={id}&cc={country_code}&l=english&filters=price_overview,basic. Pass an appid and a two-letter country code, and Steam returns the local-currency price, discount percentage, and a human-formatted price string for that region.
The endpoint is public, unauthenticated, and used by community projects like SteamDB and IsThereAnyDeal. The constraints are practical: one country per request, IP geolocation gates some regions, and a rate limit of roughly 200 requests per 5-minute rolling window per IP.
Does Steam have a regional pricing API?
No official one. Valve publishes no supported regional pricing API and no bulk-export endpoint. The appdetails URL is an internal endpoint the Steam Store web UI calls, with a stable shape the developer community has documented well — see SteamDB's API documentation and community wikis — but no stability guarantees. The Actor absorbs shape changes: ConfigDict(extra="ignore") on the Pydantic models swallows any additive fields Steam adds.
What the data looks like
Each (appid × country_code) pair produces one flat row, with the same 15 fields whether the game is paid, free, or regionally unavailable — dense, never sparse. The canonical Cyberpunk 2077 record for Argentina:
{
"appid": 1091500,
"app_name": "Cyberpunk 2077",
"app_type": "game",
"country_code": "AR",
"currency": "ARS",
"initial_price_cents": 424999,
"final_price_cents": 424999,
"discount_percent": 0,
"usd_equivalent": 4.09,
"usd_conversion_rate": 0.0000096,
"is_free": false,
"is_unavailable": false,
"formatted_local_price": "ARS$ 4.249,99",
"store_url": "https://store.steampowered.com/app/1091500/?cc=AR",
"scraped_at": "2026-05-16T12:00:00.000Z"
}
Fifteen fields, typed and validated with Pydantic v2 before write. initial_price_cents and final_price_cents are always in the smallest currency unit (ARS 4,249.99 → 424999 centavos), and is_free / is_unavailable are first-class booleans so you never infer absence from null fields.
The naive approach (and why it falls apart) ⚙️
The first pass: loop over country codes, call appdetails once per code with requests.get(), collect the results. It breaks in three ways that explain why a hosted Actor earns its keep:
1. Rate-limit arithmetic at scale. Sixty regions times 10 games is 600 requests, plus one US-price prefetch per game — 610 total. Steam's limit is ~200 per 5 minutes per IP, and a naive loop hits that ceiling after 200 requests, returning 429s with no Retry-After header. We batch at 200 with a mandatory 60-second inter-batch sleep and a 0.3-second intra-batch delay, logging progress before each sleep so a partial failure is always visible.
2. IP geolocation for restricted regions. The cc= parameter is not the only thing that determines which price you get. For some regions — Russia and China most commonly — Steam gates pricing on the request IP's geolocation, so a US datacenter IP hitting cc=RU may return a US-fallback price or success: false. We route through Apify Proxy via the BUYPROXIES94952 group by default, rotate the session on every 429 or 5xx, retry with exponential backoff (max 5 attempts), and honour Retry-After when Steam provides it.
3. The USD-conversion problem without an FX API. Regional prices arrive in local currencies — ARS, TRY, INR, BRL — and comparing them needs an exchange rate, which means latency, cost, and rate-source disagreement. We sidestep it: for each appid we fetch the US price once (cc=US), then derive the USD equivalent with Steam's own pricing — round((final_price_cents / initial_price_cents) * (us_initial_cents / 100), 4). The benchmark is Steam, not a bank. When a game is free (is_free=True) or unavailable (is_unavailable=True), we still emit the row — first-class signals, not silent drops.
The Actor 🔥
The scraper is packaged as an Apify Actor: Steam Regional Price Scraper. Run it from the Apify Console input form, or programmatically with the Python SDK:
from apify_client import ApifyClient
client = ApifyClient("YOUR_APIFY_TOKEN")
# Fetch Cyberpunk 2077 + CS2 across three regions
run = client.actor("DevilScrapes/steam-regional-price").call(
run_input={
"appids": [1091500, 730],
"countryCodes": ["US", "DE", "AR"],
"computeUsdEquivalent": True,
"useProxy": True,
}
)
for item in client.dataset(run["defaultDatasetId"]).iterate_items():
print(item["app_name"], item["country_code"], item["usd_equivalent"])
Prefer game names? Pass appNames instead of appids — the Actor fetches Steam's GetAppList (~150k entries) once, builds an in-memory name→appid map, and matches case-insensitively (includeDlc=false drops DLC entries by default).
Input fields at a glance:
| Field | Type | Default | Notes |
|---|---|---|---|
appids |
integer[] | — | XOR with appNames; 1–500 per run |
appNames |
string[] | — | XOR with appids; 1–100 per run |
countryCodes |
string[] | 60-region default | Leave empty for the full curated list |
includeDlc |
boolean | false |
Only applies when resolving by appNames
|
computeUsdEquivalent |
boolean | true |
Adds one US-price fetch per appid |
useProxy |
boolean | true |
Routes via Apify Proxy for accurate regional prices |
The XOR constraint between appids and appNames is enforced at the Pydantic validation layer — pass both, or neither, and the Actor fails before a single network call, so no charge beyond the actor-start event.
What you'd actually use this for 💡
Five concrete scenarios drawn from the README and spec:
1. Indie dev regional pricing audit. Pull your own appid plus competitor games across the 60-region default and compare usd_equivalent distributions. If your AR price sits above the regional median for your genre, you know where to tune.
2. AAA price-discrimination study. Track the ratio of usd_equivalent (AR) to final_price_cents / 100 (US) across a basket of titles over time — Steam's regional price-discrimination coefficient, the kind of number that ends up in a pricing paper.
3. Regional discount tracking. Schedule a daily run on a watchlist of 50 games. When discount_percent > 0 appears in a region where yesterday it was 0, that's an early signal of a regional promo — sales often roll out regionally before the front page.
4. Catalog availability monitoring. Count is_unavailable=true rows per region week over week. Rising unavailability signals a quiet content-policy change by Valve.
5. "Cheapest Steam region in 2026" journalism. Run 100 AAA titles across the 60-region default, sort by usd_equivalent ascending per title, publish the cheapest region per game as a table. One Actor run — the usd_equivalent column does the currency normalization for you.
Pricing — exact numbers 💰
Pay-per-event — you pay for rows that land in your dataset, nothing for rows that don't.
| Event | Price (USD) | When |
|---|---|---|
actor-start |
$0.05 | Once per run at boot |
result-row |
$0.001 | Per (appid × country) row written |
| Run size | Rows | Cost |
|---|---|---|
| 1 game × 60 regions | 60 | $0.11 |
| 10 games × 60 regions | 600 | $0.65 |
| 50 games × 60 regions | 3,000 | $3.05 |
| 100 games × 60 regions | 6,000 | $6.05 |
At scale the per-row charge dominates: roughly $1.05 per 1,000 rows. Apify's $5 free trial credit covers your first ~4,900 rows with no credit card required.
The technically interesting part
The USD-equivalent field avoids an FX API entirely by using Steam's own pricing as its benchmark:
usd_equivalent = round((final_price_cents / initial_price_cents) * (us_initial_cents / 100), 4)
This captures both the regional price level and any active discount in a single USD-denominated number with no external rate source. It's not an FX conversion — it's "what this regional price would cost if the same discount applied at the US tier," so you're measuring Valve's regional pricing decisions, not currency-market noise. When the US price is unavailable, both usd_equivalent and usd_conversion_rate are set to null rather than propagating a garbage value. And every country code is validated against ^[A-Z]{2}$ at input time — pass "us" instead of "US" and the Actor fails fast before any network call, not after 20 minutes of empty output.
Limitations
- One snapshot per run. Steam does not expose historical prices. Time-series tracking requires Apify Schedules to run the Actor on a cadence into a named dataset.
- Price + availability only. No reviews, screenshots, genre tags, player counts, or Metacritic scores — use a dedicated Steam metadata Actor for those.
-
Regional IP gating. Russia and China may return US-fallback prices or
is_unavailable=truewithout a geographically appropriate exit IP. Proxy ON (the default) mitigates this; it doesn't guarantee a country-specific IP for every region on every run. - Steam rate limit. ~200 requests per 5 minutes per IP. Large runs (50+ games × 60 regions = 3,000+ requests) take 15+ minutes due to mandatory inter-batch sleeps — the rate-limit window Steam publishes, enforced explicitly.
-
appidscap at 500;appNamescap at 100 per run. Split larger workloads across runs and concatenate — intentional, to keep each run inside the rate-limit window. - 7-day default storage retention on the Apify FREE tier. Export immediately after the run or use a named dataset for persistence beyond 7 days.
FAQ
Is scraping Steam's appdetails endpoint legal?
The endpoint is publicly accessible, requires no authentication, and has been used continuously by community tools like SteamDB and IsThereAnyDeal for years. The Actor reads only public regional price data Valve already publishes on Steam Store web pages, at a paced request rate within the documented limit. As always, consult your own legal counsel for your jurisdiction and use case.
Does Steam have an official API I can use instead?
Valve publishes the Steam Web API for partner-facing features like authentication and inventory. It does not include a supported regional pricing endpoint. The appdetails URL used here is an undocumented internal endpoint with a stable, community-documented schema; see SteamDB's API reference.
Can I export the results to Google Sheets or a data warehouse?
Yes. Export CSV, Excel, JSON, or XML from the Apify Console after the run, webhook the dataset on ACTOR.RUN.SUCCEEDED into Make, Zapier, or n8n, or pull it directly via the Apify API: GET /datasets/{id}/items?format=csv&clean=true.
Why does Argentina (AR) show usd_equivalent around $4 for a $60 game?
Steam applies explicit regional pricing tiers — Valve sets recommended local-currency prices per region, and Argentina has historically priced AAA titles far below the US tier in USD-equivalent terms. The usd_equivalent field uses Steam's own US price as the benchmark; it reflects the regional pricing decision, not an FX-rate conversion, and updates when Valve changes the tier.
Try it
The Actor is live on the Apify Store: apify.com/DevilScrapes/steam-regional-price.
Free $5 trial credit, no credit card. Run it on appid 1091500 (Cyberpunk 2077) across the 60-region default and you'll have a full regional pricing matrix in under 5 minutes. Got a use case I missed, or a field you need that isn't in the 15 already there? Drop it in the comments.
Built by Devil Scrapes — Apify Actors with attitude. Pay-per-event, transparent pricing, no junk fields. 😈
Top comments (0)