Skip to content
API

Steam profile data

Steam profile API for server-side applications

GET /v1/profile accepts a SteamID64, profile URL or vanity name and returns the resolved public Steam profile fields.

GET /v1/profileBearer authentication

Updated

curl
curl --fail-with-body --max-time 35 \
  -H "Authorization: Bearer $WOK_API_KEY" \
  "https://woksteamapi.com/v1/profile?steam_id=76561198090744629"
SteamID64profile URL or vanity input
86,400sdefault profile cache TTL
64 profilesmaximum batch profile lookup
Visibilityofficial profile state

From a Steam identity to your first profile response

A Steam profile lookup answers who the player is. It does not include their inventory, ban history or permission to trade.

Store the returned steamid as the stable identifier, not personaname. Optional country and visibility fields can be absent; no endpoint unlocks private profile data.

For a roster, POST /v1/profiles accepts {"steamids":["76561198090744629"]} with up to 64 numeric IDs. Each ID consumes a quota unit. Use the inventory API separately for owned items.

Profile rows have a configurable 86,400-second default TTL. cached distinguishes a cache hit; the profile response does not expose exact cache age or the inventory freshness headers.

Build a player card with Steam identity and FACEIT rank

Download the Steam + FACEIT Python example and follow the setup and run instructions. Resolve the Steam identity first, then send its returned steamid as game_player_id to the FACEIT workflow. Use the FACEIT response's player_id for any later stats or history requests.

Keep the two results separate: a FACEIT profile 404 means no linked CS2 player was found; it does not erase a valid Steam profile. Optional fields and missing statistics stay unavailable. Steam and FACEIT reads consume separate quota units and use separate caches, so a newly fetched card does not establish that every field was freshly observed. Store the WOK key in your server environment.

Previous names, Steam level and game hoursProfile privacy and missing dataProfile and friends referenceCompatibility endpoint migration checklistChoose a quota for your player count

Keep successful profiles when a roster lookup is partial

For a roster, a missing player must stay unavailable while successful player cards remain usable. HTTP 200 alone does not establish that every requested profile was returned.

First inspect GET /v1/key with your server-held WOK key. Send include_meta: true only when capabilities.profile_batch_details.version is 1. Without this option, POST /v1/profiles keeps its existing SteamID-to-profile map. The detailed response adds profiles, missing and meta; it does not increase the 64-ID limit or unlock private data.

Capture one requested roster

Export WOK_API_KEY in your trusted server environment, replace the two example IDs with your selected numeric SteamID64 strings and run this request once. Both IDs are illustrative. Keep the saved response and headers private; no API key belongs in a browser bundle.

Save a detailed profile batch
umask 077
# Replace both IDs with the same roster you will pass to the reader.
curl --disable --fail-with-body --silent --show-error \
  --max-time 35 --max-filesize 1048576 \
  -H "Authorization: Bearer $WOK_API_KEY" \
  -H "Content-Type: application/json" \
  --data '{"steamids":["76561198000000001","76561198000000002"],"include_meta":true}' \
  --dump-header profile-headers.txt --output profiles.json \
  --write-out '\nHTTP %{http_code}\n' \
  "https://woksteamapi.com/v1/profiles"

Read the HTTP status and Retry-After in profile-headers.txt. An accepted all-failed request can return a detailed body with HTTP 503; authentication, quota and input errors keep their usual error shape. --fail-with-body saves an error body and exits unsuccessfully. Check it before deciding what to do; this command does not retry.

Saved outcomeApplication decisionWhat it establishes
One profile and one retryable 503Keep the returned profile; consider only the missing ID after the required delay.The other profile is unavailable for this read. It is not a private or nonexistent account finding.
steam_profile_not_returned, 404, retryable: falseShow that player's data as unavailable; do not retry it as a temporary source failure.An official response omitted that ID. It proves no country, inventory contents or clean ban history.
All requested IDs returnedUse each profile's own source and observation time.meta.complete covers requested identities, not every optional field or current inventory visibility.

Read the saved results without another API call

Save this code as profile_batch_view.py, then run python3 profile_batch_view.py 76561198000000001 76561198000000002 with the same IDs you actually requested. It checks identity coverage and returns successful cards, explicit missing states and retry candidates. It makes no requests and does not retry any ID. This focused check is not a complete JSON Schema validator or proof that a saved response is authentic.

Python reader for saved profile results
Inspect a saved partial roster
# Save as profile_batch_view.py; this reads saved JSON and makes no requests.
import json
import math
import re
import sys


def need(condition, message):
    if not condition:
        raise ValueError(message)


def batch_view(data, wanted):
    need(1 <= len(wanted) <= 64 and all(
        isinstance(sid, str) and re.fullmatch(r"[0-9]{17}", sid) for sid in wanted),
        "Supply 1..64 numeric SteamID64 strings from the request")
    wanted = list(dict.fromkeys(wanted))
    need(isinstance(data, dict), "Expected a detailed batch object")
    profiles, missing, meta = (data.get(key) for key in ("profiles", "missing", "meta"))
    need(isinstance(profiles, dict) and isinstance(missing, list)
         and isinstance(meta, dict), "Detailed response unavailable; check HTTP status and capability")
    need(type(meta.get("version")) is int and meta["version"] == 1,
         "Unsupported details version")
    need(isinstance(meta.get("request_id"), str)
         and re.fullmatch(r"[0-9a-f]{32}", meta["request_id"]), "Invalid request ID")
    for key in ("requested", "returned", "charged_units"):
        need(type(meta.get(key)) is int and meta[key] >= 0, "Invalid batch count")
    need(meta.get("quota_admitted") is True and meta["requested"] == len(wanted)
         and meta["returned"] == len(profiles) and meta["charged_units"] == len(wanted),
         "Request identity or admitted quota does not match")
    need(set(profiles) <= set(wanted) and len(missing) <= len(wanted),
         "Unexpected result IDs")
    cards = {}
    for sid, profile in profiles.items():
        need(isinstance(profile, dict) and profile.get("steamid") == sid,
             "Profile identity does not match")
        need(profile.get("personaname") is None or isinstance(profile["personaname"], str),
             "Invalid profile name")
        need(profile.get("source", "unknown") in
             ("steam_webapi", "steam_public_profile_xml", "unknown"), "Invalid profile source")
        stamp = profile.get("source_observed_at")
        try:
            known_time = type(stamp) in (int, float) and math.isfinite(stamp) and stamp > 0
        except OverflowError:
            known_time = False
        cards[sid] = {"steamid": sid, "personaname": profile.get("personaname"),
                      "source": profile.get("source", "unknown"),
                      "source_observed_at": stamp if known_time else None}
    failures, retry_ids, waits = {}, [], []
    for row in missing:
        need(isinstance(row, dict), "Invalid missing result")
        sid, error = row.get("steamid"), row.get("error")
        need(isinstance(sid, str) and sid in wanted and sid not in cards and sid not in failures,
             "Duplicate, overlapping or unexpected missing ID")
        need(type(row.get("retryable")) is bool and isinstance(error, dict)
             and isinstance(error.get("code"), str) and bool(error["code"])
             and type(error.get("status")) is int and 400 <= error["status"] <= 599,
             "Invalid per-ID error")
        delay = error.get("retry_after")
        need(delay is None or type(delay) is int and 1 <= delay <= 86400,
             "Invalid retry delay")
        if row["retryable"]:
            need(error["status"] in (429, 502, 503, 504), "Unexpected retry classification")
            retry_ids.append(sid)
            if delay is not None:
                waits.append(delay)
        failures[sid] = {"code": error["code"], "status": error["status"],
                         "retryable": row["retryable"]}
    need(set(cards) | set(failures) == set(wanted), "Batch does not cover the requested IDs")
    need(type(meta.get("complete")) is bool and meta["complete"] == (not failures),
         "Inconsistent completion flag")
    return {"profiles": cards, "missing": failures, "retry_candidates": retry_ids,
            "per_id_minimum_delay": max(waits) if waits else None,
            "charged_units": meta["charged_units"], "request_id": meta["request_id"]}


def reject_constant(value):
    raise ValueError("Non-finite JSON constant: " + value)


if __name__ == "__main__":
    try:
        with open("profiles.json", "rb") as saved:
            raw = saved.read(1048577)
        need(len(raw) <= 1048576, "Saved response exceeds the 1 MiB size limit")
        result = batch_view(json.loads(raw, parse_constant=reject_constant), sys.argv[1:])
        print(json.dumps(result, ensure_ascii=True, allow_nan=False, indent=2))
    except (OSError, ValueError, RecursionError) as error:
        raise SystemExit(str(error))

Before a later request, honor the maximum of the response's Retry-After, available per-ID delays and your application's bounded backoff with jitter. A missing delay is unknown, not permission to retry immediately. Retry only explicitly retryable missing IDs, with a cap; replaying the whole roster charges successful IDs again.

After quota admission, each unique requested ID is charged once even when local capacity fails before a Steam attempt. meta.charged_units reports this WOK quota policy, not proxy cost or delivered profiles. A later retry consumes quota again. source_observed_at is a local observation time retained on cache hits, not a Steam historical snapshot or a promise of freshness; null means unknown. cached alone does not supply exact age. A profile's source and visibility do not establish inventory access, country or ban history for a missing peer.

Full profile batch contractSeparate profile and inventory privacyAuthentication, quota and source errorsShare the request ID with support

Quickstart

Use WOK from a trusted server. Keep the API key outside browser bundles and public repositories.

curl
curl --fail-with-body --max-time 35 \
  -H "Authorization: Bearer $WOK_API_KEY" \
  "https://woksteamapi.com/v1/profile?steam_id=76561198090744629"
JavaScript (Node.js)
// Node.js: keep the API key on your server.
const response = await fetch(
  "https://woksteamapi.com/v1/profile?steam_id=76561198090744629",
  {headers: {Authorization: `Bearer ${process.env.WOK_API_KEY}`}}
);
if (!response.ok) throw new Error(`${response.status}: ${await response.text()}`);
const profile = await response.json();
Python
import os
import requests

response = requests.get(
    "https://woksteamapi.com/v1/profile",
    headers={"Authorization": f"Bearer {os.environ['WOK_API_KEY']}"},
    params={"steam_id": "76561198090744629"},
    timeout=30,
)
response.raise_for_status()
profile = response.json()

Response example

The values below illustrate the response shape. Field names and types match the public contract.

JSON

application/json
{
  "steamid": "76561198090744629",
  "personaname": "Example player",
  "loccountrycode": "US",
  "avatarfull": "https://avatars.steamstatic.com/..._full.jpg",
  "profileurl": "https://steamcommunity.com/profiles/76561198090744629/",
  "visibility": 3,
  "cached": false
}

Response schema

Stable fields for typed clients, storage and error handling.

Steam profile API for server-side applications response schema
FieldTypeMeaning
steamidstringResolved 17-digit SteamID64.
personanamestringCurrent Steam display name.
loccountrycodestringCountry code when Steam exposes it.
avatarfullstringFull-size Steam avatar URL.
profileurlstringCanonical Steam community profile URL.
visibilityintegerSteam community visibility when supplied; 0 or absent means unknown.
cachedbooleanTrue when served from WOK's profile cache.

Limits and error behavior

Rules clients should handle explicitly in production.

Private profiles can omit fields that Steam does not expose. Do not infer absence from an empty optional string.

The single-profile endpoint returns 404 when a profile cannot be resolved. Batch profile lookup accepts up to 64 SteamIDs.

Use this API from a trusted server. Do not ship a WOK API key in browser JavaScript or a desktop client.

Cache and freshness semantics

How to tell whether data was reused, shared or refreshed.

Profile rows use a configurable 86,400-second default TTL. The cached boolean tells whether this response used that row.

A fresh official visibility result can invalidate older positive inventory snapshots when the profile becomes private.

Profile responses do not expose inventory cache headers; those headers apply to /v1/inventory and /steam/api/inventory.

Frequently asked questions

Direct answers about access, freshness and result semantics.

Can the profile API resolve a vanity URL?

Yes. The endpoint accepts a SteamID64, Steam profile URL or vanity name and returns the resolved public fields.

Does WOK expose private profile data?

No. Optional fields can be absent when Steam does not expose them publicly.