Loading documentation…
Loading documentation…
Choose Live Page, Current, or Refresh and implement the full application workflow.
Most applications need two steps: decide whether the inventory needs updating, then read Current pages. Refresh asks SteamCall to collect and save the complete inventory. Your application chooses when to refresh and what to show while it waits.
| Application | Recommended flow | Why |
|---|---|---|
| Profile, trading tool, bot, owner dashboard | Current; Refresh on user action or freshness expiry | Fast reads and complete revision identity |
| Background synchronization | Scheduled Refresh with jitter; poll; consume Current | Complete history without duplicate Steam loops |
| Search/index builder | Refresh; Current all pages; index by revision_id | Never indexes a partial scan |
| One-off preview needing only one or two pages | Live Page | Avoids paying for collection you will not use |
| Existing page-by-page Steam adapter | Live Page during migration, then move to Refresh/Current | Preserves behavior while adopting managed state |
Do not call Refresh before every Current read: Current never refreshes automatically. Successful Live Page requests do not guarantee that a complete saved inventory is available. Use Refresh when you need to track complete collection and its final charge.
inventory_revision_not_found, or your freshness limit is exceeded, submit Refresh with a new idempotency key.refresh_id; poll status using Retry-After and a deadline. Status costs no credits but still counts toward your Service Account's request limit.classid + instanceid.Private, empty, unavailable, and never-refreshed are separate states. Keep request IDs, status codes, and structured error codes in safe diagnostics; never log the API key or full inventory payload.
Install requests with python -m pip install requests, set STEAMCALL_API_KEY in your server environment, and replace the example Steam ID. This example explicitly starts a paid Refresh; use it when a fresh scan is needed, not on every page view. Save it as inventory.py and run python inventory.py.
import os
import time
import uuid
import requests
BASE = "https://api.steamcall.com"
KEY = os.environ["STEAMCALL_API_KEY"]
SCOPE = "/v2/steam/users/76561198000000000/inventory/440/2"
session = requests.Session()
session.headers["X-API-Key"] = KEY
refresh = session.post(
BASE + SCOPE + "/refresh",
headers={"Idempotency-Key": str(uuid.uuid4())},
json={"language": "english", "max_credits": 125},
timeout=(3, 15),
)
refresh.raise_for_status()
refresh_id = refresh.json()["data"]["refresh_id"]
deadline = time.monotonic() + 120
status = refresh
while True:
delay = max(1, int(status.headers.get("Retry-After", "2")))
if time.monotonic() + delay >= deadline:
raise RuntimeError(f"resume refresh {refresh_id} later; polling deadline reached")
time.sleep(delay)
status = session.get(
f"{BASE}/v2/steam/inventory-refreshes/{refresh_id}",
timeout=(3, 15),
)
status.raise_for_status()
result = status.json()["data"]
state = result["state"]
if time.monotonic() >= deadline:
raise RuntimeError(f"resume refresh {refresh_id} later; polling deadline reached")
if result["billing"]["state"] in {"reserved", "pending"}:
continue
if state in {"completed", "failed", "cancelled"}:
if state != "completed" or result["outcome"] not in {
"complete_changed", "complete_unchanged"
}:
raise RuntimeError(f"refresh outcome: {result['outcome']}")
break
assets = []
descriptions = {}
cursor = None
seen_cursors = set()
revision_id = None
read_deadline = time.monotonic() + 120
for page_number in range(125):
if time.monotonic() >= read_deadline:
raise RuntimeError("Current traversal deadline reached; discard partial result")
params = {"language": "english", "page_size": 2000}
if cursor:
params["cursor"] = cursor
response = session.get(BASE + SCOPE + "/current", params=params, timeout=(3, 15))
response.raise_for_status()
if time.monotonic() >= read_deadline:
raise RuntimeError("Current traversal deadline reached; discard partial result")
page = response.json()["data"]
if revision_id is None:
revision_id = page["revision_id"]
if page["revision_id"] != revision_id:
raise RuntimeError("Current revision changed; discard partial result")
assets.extend(page["assets"])
descriptions.update({
(item["classid"], item["instanceid"]): item
for item in page["descriptions"]
})
cursor = page["pagination"]["next_cursor"]
if not cursor:
break
if cursor in seen_cursors:
raise RuntimeError("Current cursor repeated; discard partial result")
seen_cursors.add(cursor)
else:
raise RuntimeError("Current page limit reached; discard partial result")The example waits for both collection and billing, then reads only successful complete outcomes. It honors the API's Retry-After seconds, including the accepted response, and bounds polling and pagination. A deadline stops this client; it does not cancel the refresh or release its credits. Resume with the same saved refresh ID rather than submitting a replacement.
In production, persist the refresh ID and idempotency key, parse documented error envelopes before raise_for_status, and capture X-Request-ID. Keep this work outside a browser request. The Requests timeouts bound connection and read inactivity, not exact total wall-clock time; use your job runner's deadline for a strict overall limit. Publish assets only after the entire traversal succeeds; never publish a partial result after an exception.
This helper reads one Current page; it does not request a refresh. Use a server-side TypeScript runtime with fetch and AbortSignal.timeout support. Set STEAMCALL_API_KEY and replace the example Steam ID before calling currentPage().
const base = "https://api.steamcall.com";
const scope = "/v2/steam/users/76561198000000000/inventory/440/2";
const key = process.env.STEAMCALL_API_KEY;
if (!key) throw new Error("Set STEAMCALL_API_KEY in your server environment");
const headers = { "X-API-Key": key };
async function currentPage(cursor?: string) {
const url = new URL(`${base}${scope}/current`);
url.searchParams.set("language", "english");
url.searchParams.set("page_size", "2000");
if (cursor) url.searchParams.set("cursor", cursor);
const response = await fetch(url, {
headers,
signal: AbortSignal.timeout(15_000),
});
const requestId = response.headers.get("x-request-id");
const body = await response.json();
if (!response.ok)
throw new Error(`${body.error?.code ?? response.status} (${requestId})`);
return body.data;
}Keep this module server-side. Browser code must call your backend so the credential cannot enter a bundle, mobile binary, extension, or client log.
Persist the SteamCall revision_id, observation_id, scan window, verified time, and your synchronization state separately from rendered item rows. An indexer can make revision_id its idempotency key: seeing the same revision twice is a no-op. When a new revision appears, replace or diff your projection transactionally.
Do not claim an exact ownership transfer time from two observations. SteamCall can prove that an asset was observed in complete revisions and expose gaps; it cannot prove continuous ownership between observations.
If several parts of your application can request the same refresh, avoid submitting duplicate user actions or scheduled jobs. SteamCall can share simultaneous collection work, but each customer still receives their own refresh status and billing result.
Continue with Synchronize inventory data, Current inventory, Refresh inventory, and Inventory errors.