# SteamGPT API - full guide for AI agents

> Base URL: https://steamgpt.net | no API key, no registration | no cookies, no tracking | soft limit 120 req/min per IP
> SteamGPT is an independent service - not affiliated with Valve Corporation or Steam.

## Endpoints

| Endpoint | Returns | When to use |
| --- | --- | --- |
| `GET /summary/{id}[.md\|.json\|.ai]` | Steam summary + Steam bans + FACEIT + first 100 entries of the public friend graph | need everything in one request (~0.5-1.5k tokens; preset=competitive ~350) |
| `GET /profile/{id}[.md\|.json\|.ai]` | Steam summary only | cheapest (~200 tokens; .ai ~80) |
| `GET /friends/{id}[.md\|.json\|.ai]` | public Steam friend graph, platform snapshot (?limit=N max 5000 default 100; ?detail=short\|medium\|full) | only the friend graph (short: ~5 tokens/friend) |
| `GET /faceit/{id}[.md\|.json\|.ai]` | FACEIT player object + bans | only FACEIT (~100-200 tokens) |
| `GET /batch/{id1,id2,...}[.md\|.json\|.ai]` | up to 100 Steam profiles at once (any SteamID format except vanity names) | bulk lookups, 1 request instead of 100 |
| `GET /converter/{id}[.md\|.json\|.ai]` | SteamID converter: steamid64 (dec + hex), SteamID, legacy STEAM_0, SteamID3, 32-bit account id, steam:hex, profile URL - all at once | convert ids instantly (~120 tokens) |
| `GET /identity/{id}[.md\|.json\|.ai]` | identifier resolver THROUGH Steam: adds vanity resolution to the conversions above | resolve vanity names (~100 tokens) |
| `GET /bans/{id}[.md\|.json\|.ai]` | Steam bans: VACBanned, NumberOfVACBans, NumberOfGameBans, DaysSinceLastBan, CommunityBanned, EconomyBan | cheating reports, trust checks (~100 tokens) |
| `GET /compare/{id1}/{id2}[.md\|.json\|.ai]` | two players side by side: identity, account, bans, FACEIT, shared entries of the public friend graph | head-to-head checks |

`{id}` accepts ANY SteamID form: steamid64 (17 digits), STEAM_1:0:x / STEAM_1:1:x, [U:1:x], a steamcommunity.com profile link (/profiles/ or /id/) or a vanity name. Resolution goes through Steam.

`/batch` accepts comma-separated ids in ANY computable SteamID format (steamid64, STEAM_1:0:x, STEAM_0:0:x, [U:1:x], hex, steam:hex, a 32-bit account id) - only vanity names are excluded, since each one would need its own Steam lookup. It returns `{"count", "players": [{"steamid64", "ids", "steam"}], "not_found": [...], "invalid": [...]}`. Over 100 ids - HTTP 400. Also available as POST /batch with JSON body {"ids": ["765611...", ...]} - same limits, better for applications; GET stays for agents.

`/summary` has agent presets: `?preset=identity` (Steam block only), `?preset=competitive` (FACEIT + bans, no friends), `?preset=full` (= no preset). Explicit `?include=` wins over preset.

`/summary` response size is controlled by `?include=` (comma list of faceit, friends, bans - omit to get all three), `?friends_limit=N` (alias `?limit=`, default 100, max 5000) and `?friends_detail=short|medium|full`. Example: `/summary/{id}.json?include=faceit,bans` returns no friends at all; `?include=steam` is the leanest summary. AI agents: default to `?friends_detail=short` unless friend metadata is required.

`/batch` accepts `?include=faceit,bans` too: FACEIT for the whole batch is one query, Steam bans for the whole batch are one upstream call - the cheapest way to review a full match roster.

`/identity` performs resolution only (no profile fetch): `{"steamid64", "steamid", "steamid3", "steam_hex", "vanity", "profile_url", "resolved": true}`. `vanity` is the custom URL name when known to the cache, `null` otherwise.

## Quick start from code

```python
import requests
data = requests.get("https://steamgpt.net/summary/76561197960287930.json?preset=competitive").json()["data"]
```

```js
const { data } = await ( await fetch("https://steamgpt.net/summary/76561197960287930.json?preset=competitive") ).json()
```

More snippets (LangChain, MCP for Claude/Cursor/VS Code): https://steamgpt.net/ai.md

## Authentication (optional)

No credential is required - every call above works anonymously. A free self-service token (no registration, no client_id) raises the per-IP limit from 120 to 600 requests/min:

```bash
TOKEN=$(curl -s -X POST https://steamgpt.net/oauth/token -d "grant_type=client_credentials" | jq -r .access_token)

curl -H "Authorization: Bearer $TOKEN" https://steamgpt.net/summary/76561197960287930.json
```

The token is a 1-hour JWT with scope `rate-boost`; mint a new one when it expires. An invalid token returns HTTP 401 `invalid_token` - drop the header and the API keeps working. Full discovery document: https://steamgpt.net/auth.md

## Format selection

1. Suffix wins: `.md`, `.json` or `.ai`
2. Then `?format=md|json|ai`
3. Then `Accept: text/markdown` / `application/json` / `text/html`
4. No browser Accept header - markdown by default (token-cheapest)

`.ai` returns a deterministic plain-text rendering (text/plain) built by a serializer, not an LLM - fast, stable and the cheapest to read for a model. Available on every data endpoint: /summary /profile /bans /faceit /friends /identity /batch /compare.

## Example: request

```
curl https://steamgpt.net/profile/76561197960287930.md
```

## Example: response shape (JSON)

```json
{
  "result": "success",
  "data": {
    "steamid64": "76561197960287930",
    "ids": {"steamid64": "...", "steamid": "STEAM_1:0:...", "steamid3": "[U:1:...]"},
    "steam": {"steamid": "...", "personaname": "...", "profileurl": "...", "avatarfull": "...", "timecreated": 0, "personastate": 0}
  }
}
```

TypeScript types for every response: https://steamgpt.net/types.d.ts (optional = field absent, | null = present but null).

Steam data freshness: `/profile` and `/summary` serve a cache no older than 48 hours - stale or unknown profiles are refreshed live from Steam on request. `/batch` and `/friends` are cache-only (fastest, no live refresh).

JSON responses of /profile and /summary include machine trust blocks: `provenance` ({source, retrieved_at unixtime|null, age_seconds|null, fresh boolean, cache_ttl seconds|null} per data source; fresh = age within TTL) and `canonical` ({steamid64, url}) - /profile/{vanity} and /profile/{steamid64} are the same resource. /faceit and /bans carry `provenance` too (no canonical block).

Versioning: current major is v1. /v1/{endpoint} is an alias of every unversioned path; breaking changes will only land under /v2.

Conditional GET: every response carries a strong ETag and Cache-Control: public, max-age=60. Send If-None-Match to get 304 Not Modified.

All timestamps are unixtime. Markdown responses show dates as `YYYY-MM-DD HH:MM:SS UTC | unixtime: N`.

Friend detail levels (`?detail=`): `short` - plain steamid64 array (cheapest), `medium` - `{"steamid64", "personaname"}`, `full` (default) - `{"steamid64", "steam": {raw Steam summary object}}`.

FACEIT data is the FACEIT API player object: `/faceit` returns `{"steamid64", "faceit": {player object}, "bans": [...]}`; `/summary` mirrors it as `data.faceit` + `data.faceit_bans`.

## Use with AI tools

- ChatGPT: Custom GPT -> Actions -> import https://steamgpt.net/openapi.json (no auth)
- Claude: paste https://steamgpt.net/llms.txt into the chat, or let the agent GET any endpoint directly
- Cursor / IDE agents: add https://steamgpt.net/llms-full.txt to project docs; typed clients via /types.d.ts
- OpenAI Agents / tool use: generate a typed tool from /openapi.json; responses match /types.d.ts
- MCP: Streamable HTTP endpoint at https://steamgpt.net/mcp - `claude mcp add --transport http steamgpt https://steamgpt.net/mcp`. 9 read-only tools: steam_convert, steam_identity, steam_bans, steam_profile, steam_faceit, steam_friends, steam_summary, steam_batch, steam_compare (listed cheapest first). No auth. Manifest: https://steamgpt.net/.well-known/mcp-server.json. Official MCP Registry entry: net.steamgpt/steamgpt (verified on Glama: glama.ai/mcp/servers/SteamGPTnet/steamgpt-mcp). Stdio-only clients: npx -y steamgpt-mcp. JS SDK: npm install steamgpt
- Context7 (docs in coding-agent context): libraries `/steamgptnet/steamgpt-js` and `/steamgptnet/steamgpt-mcp`

## The .ai format

`.ai` is a DETERMINISTIC flat-text serializer (not an LLM summary): short labeled lines, no markdown tables, no JSON braces - the cheapest representation to read for any model. Same data every time for the same input. Real example, `GET /profile/76561197960287930.ai`:

```
Steam user Rabscuttle.

Identity:
SteamID64: 76561197960287930
SteamID: STEAM_1:0:11101
SteamID3: [U:1:22202]

Account:
Profile is private.
Currently offline.
```

Use `.ai` when the model just needs facts, `.md` when a human may read the output too, `.json` when code parses it.

## /friends detail levels

- `detail=short` - bare array of steamid64 strings: `["76561197960287930", ...]` (~5 tokens/friend)
- `detail=medium` - `[{"steamid64", "personaname"}]` - id + current nickname
- `detail=full` (default) - `[{"steamid64", "steam": {...}}]` - raw Steam player object per friend (avatar, profile url, visibility, country...), the same shape as `data.steam` of /profile

## Errors

Errors follow the same format selection as success responses: `.md` suffix - markdown error, `.json` - JSON, browser Accept - HTML page; no format hints - markdown, same default as success.

JSON error shape: `{"result": "error", "code": 404, "error": "player_not_found", "message": "..."}`. Switch on the `error` field:

Real error bodies:

`GET /profile/zzz-no-such-user.json` -> HTTP 404
```json
{"result": "error", "code": 404, "error": "player_not_found", "message": "Player not found. Use steamid64 (17 digits), STEAM_1:0:x, [U:1:x], a steamcommunity.com link or a vanity name."}
```

`GET /profile/zzz-no-such-user.md` -> the SAME error in the format you asked for:
```
# Error 404: player_not_found
> Player not found. Use steamid64 (17 digits), STEAM_1:0:x, [U:1:x], a steamcommunity.com link or a vanity name.

Docs: https://steamgpt.net/docs.md | https://steamgpt.net/llms.txt
```

121st request in a minute -> HTTP 429 with a `Retry-After: 60` header:
```json
{"result": "error", "code": 429, "error": "rate_limited", "message": "Soft fair-use limit: 120 requests/min per IP. Responses are cached - please slow down."}
```

101 ids in /batch -> HTTP 400 `{"result": "error", "code": 400, "error": "batch_too_large", "message": "Max 100 steamid64 per batch request."}`

- `player_not_found` (404) - id did not resolve or profile unknown to Steam
- `no_faceit_data` (404) - no FACEIT profile for this player
- `batch_too_large` (400) / `batch_no_valid_ids` (404) - /batch input problems
- `no_bans_data` (404) - Steam did not return ban data (upstream unavailable), retry later
- `bad_request` (400; 413 when the body is over 16kb) - malformed body, unknown include value or oversized body
- `unknown_endpoint` (404) - wrong path, see /llms.txt
- `invalid_token` (401) - the OPTIONAL Bearer boost-token is invalid or expired: mint a fresh one (POST /oauth/token) or drop the Authorization header, the API works without it
- `rate_limited` (429) - honor Retry-After (soft limit 120 req/min per IP; an optional free self-service token raises it to 600 - see /auth.md)
- `internal` (500) - server error, retry later

Private profiles return 200 with the public subset of fields, not 404.

## Partial responses

`/summary` merges several sources, so one of them can be down while the rest are fine. The response always carries a `sources` map and a `partial` flag:

```json
{"result": "success", "partial": true, "data": {"sources": {"steam": "ok", "faceit": "unavailable", "friends": "ok", "bans": "excluded"}}}
```

- `ok` - source answered, data is present
- `empty` - source answered, this player simply has no such data (a real fact)
- `unavailable` - source did not answer: the block in `data` is NOT evidence of absence, retry the narrow endpoint
- `excluded` - you did not request this block via include

Markdown and .ai renders say the same in words instead of silently showing an empty section. `result` stays `success` so existing clients keep working - branch on `partial` / `sources` when correctness matters.

## Freshness (trust the data)

Every JSON provenance block answers "can I trust this NOW": `{"source", "retrieved_at", "age_seconds", "fresh", "cache_ttl"}`. `fresh: true` means the snapshot age is within its TTL; `fresh: false` with a large `age_seconds` means treat online status, current game and bans as possibly stale.

## Versioning policy

Unversioned paths are a STABLE alias of `/v1` - pin `/v1/...` in long-lived tools if you want to be explicit. Breaking changes (renamed fields, changed shapes) only ever land in a future `/v2`; `/v1` semantics are frozen.

## Data caveats (read before drawing conclusions)

- The friend graph is the platform snapshot of the public Steam graph, not a live Steam query: it can be partial, so an empty intersection in /compare is NOT proof that two players are unrelated.
- `/batch` and `/friends` are cache-only: a real account the platform has never seen lands in `not_found` - re-fetch those ids with /profile, which does go to Steam.
- `DaysSinceLastBan` is 0 both for "banned today" and for "never banned" - read it together with `NumberOfVACBans` / `NumberOfGameBans`, exactly as Steam returns it.
- `provenance.retrieved_at` is the age of the cached snapshot; ban data lives with the profile snapshot (`cache_ttl` 432000 = 5 days), a stale snapshot triggers a background refresh - check `provenance.fresh`.
