RustBanned.net
Single LookupMass LookupAPIFAQ
RustBanned.net
Single Lookup
Check a single player
Mass Lookup
Check up to 100 players
API
Integrate with your server
FAQ
Common questions

© 2026 RustBanned.net

Not affiliated with or endorsed by Valve Corporation or Facepunch Studios.

© 2026 RustBanned.net. All rights reserved.

    API referencev2.0.0Looking for v1?
    Introduction
    • Overview
    • Your first request
    • The essentials
    • The envelope
    • Identifiers and include
    • Caching
    • Rate limits
    • Errors
    Endpoints
    • All endpoints
    • Look up an account
    • Batch lookup
    • Ban record only
    • Scan a friend list
    • Rust statistics
    • Rust inventory
    • Resolve an identifier
    • Image proxy
    Guides
    • Watching a roster
    • Migrating from v1
    • Before you ship

    API reference#

    A read-only HTTPS API for Steam ban data. No key, no signup. Responses are JSON.

    Version 2 adds three things worth having if you poll: include, so a request only fetches the sections you read; batches of 100 accounts; and ETag revalidation, so an unchanged account returns an empty 304. Version 1 is unchanged and stays online.

    Your first request#

    Pick your shell and paste this in. You should get JSON back.

    curl "https://rustbanned.net/api/v2/players/76561198012345678?include=bans" \
      -H "User-Agent: YourAppName/1.0 (contact@example.com)"

    curl ships with macOS, most Linux distributions, and Windows 10 build 1803 and later, so there is nothing to install. The three commands differ only in how each shell continues a line: \ in bash and zsh, a backtick in PowerShell, and no continuation in cmd. In Windows PowerShell 5.1 curl is an alias for Invoke-WebRequest, which does not accept -H, so call curl.exe by name.

    User-Agent should name your app and give a contact address. Every v2 endpoint returns 403 if it is missing or shorter than 5 characters, so call the API from a server, bot, or plugin. Browser JavaScript cannot set its own User-Agent, though the endpoints do send Access-Control-Allow-Origin: *.

    The essentials#

    Base URL
    https://rustbanned.net/api/v2
    Transport
    HTTPS only. HSTS is on, so an http:// fallback will not work.
    Authentication
    None.
    Required header
    User-Agent, at least 5 characters, on every endpoint.
    Response format
    JSON, always wrapped in the envelope. The image proxy returns bytes, and friend scans can stream events.
    Versioning
    The major version is in the path. Fields are added, never removed or retyped.
    Rate limiting
    Enforced by Cloudflare at the edge, 5,000 requests per minute per IP.
    Batch maximum
    100 identifiers per request — Steam's own per-call maximum.

    The envelope#

    Every response has the same three keys. Check ok first: on true the payload is under data, on false the reason is under error.

    Meta

    FieldTypeDescription
    meta.generatedAtstringWhen the response was produced, ISO 8601.
    meta.cachedbooleanTrue when every part of the payload came from the shared cache.
    meta.ageSecondsintegerAge of the oldest upstream document behind the response.
    meta.durationMsnumberTime the origin spent on the request.
    meta.paginationobjectlimit, offset, total, returned, hasMore. Collections only.
    200 · Success
    {
      "ok": true,
      "data": { "…": "whatever the endpoint returns" },
      "meta": {
        "generatedAt": "2026-09-03T12:00:00.000Z",
        "cached": true,
        "ageSeconds": 42,
        "durationMs": 1.71
      }
    }
    404 · Failure
    {
      "ok": false,
      "error": {
        "code": "player_not_found",
        "message": "No Steam account matches \"somevanity\".",
        "status": 404,
        "docs": "https://rustbanned.net/api-docs/v2#error-player_not_found"
      },
      "meta": {
        "generatedAt": "2026-09-03T12:00:00.000Z",
        "durationMs": 1.42
      }
    }

    Identifiers and include#

    Anywhere {steamId} appears you may pass a SteamID64, a vanity name, or a full profile URL. Percent-encode the URL form so the slashes survive the path. Resolution is cached for seven days.

    76561198012345678SteamID64
    playernamevanity name
    https%3A%2F%2Fsteamcommunity.com%2Fid%2Fplayernameprofile URL, encoded
    steamcommunity.com/groups/…group URLs are not accounts

    include decides how much upstream work a lookup does. Each section is one Steam call, so a narrower request is a faster one.

    profile
    Name, avatar, visibility, account age. One call.
    bans
    The ban record. One call, cached six hours.
    rust
    Rust playtime. One call per account — not batched by Steam.
    level
    Steam level. One call per account. Not in the default set.
    all
    Every section above.

    Caching#

    Every successful response carries a weak ETag covering the payload. The timing fields in meta are excluded from it, so the validator stays stable for as long as the data does. Send it back as If-None-Match and an unchanged resource answers 304 with no body.

    Responses are cached at the edge as well, so repeat traffic is often answered without reaching the origin. X-Cache says whether the payload came from cache, and X-Data-Age how old the Steam data behind it is.

    Response headers

    ETag
    Weak validator over the payload. Send it back as If-None-Match.
    Cache-Control
    public, max-age=0, s-maxage=…, stale-while-revalidate=… — tuned per endpoint.
    X-Cache
    HIT when the payload came from the shared cache, MISS when Steam was called.
    X-Data-Age
    Seconds since the underlying Steam data was fetched. Also in meta.ageSeconds.
    Server-Timing
    Per-stage origin timings, readable in browser devtools.
    cf-ray
    Added by Cloudflare. Identifies the request in our logs — quote it if you report a problem.
    Retry-After
    On 429 only. Seconds to wait before retrying.
    Conditional request
    # Keep the ETag from the first response
    $ curl -si "https://rustbanned.net/api/v2/players/76561198012345678/bans" \
        -H "User-Agent: YourAppName/1.0 (contact@example.com)" | grep -i etag
    etag: W/"nr5M9VZRl4w2AuOC0P9x9VOHsWs"
    
    # Send it back. Nothing changed, so there is no body to download.
    $ curl -so /dev/null -w '%{http_code}\n' \
        "https://rustbanned.net/api/v2/players/76561198012345678/bans" \
        -H "User-Agent: YourAppName/1.0 (contact@example.com)" \
        -H 'If-None-Match: W/"nr5M9VZRl4w2AuOC0P9x9VOHsWs"'
    304

    Rate limits#

    Rate limiting happens at the edge, in Cloudflare, before a request reaches the API. The limit is 5,000 requests per minute per IP. Ordinary use never approaches it; hitting it means a script, a scraper, or a client stuck in a retry loop.

    Over the limit you get 429 and an error page rather than JSON, so check Content-Type before parsing. Honour Retry-After, back off exponentially, and do not retry in a tight loop — sustained pressure gets the IP blocked outright.

    Caching is what keeps you comfortably under it. A cached response and a 304 both count as requests, but neither costs an upstream call, so conditional requests and batching are the two things that matter. One batch of 100 is one request; a hundred single lookups are a hundred.

    Errors#

    Failures use the same envelope with ok: false. Branch on error.code, which is part of the contract — messages are written for humans and may be reworded. Not every failure comes from the API, though — Cloudflare sits in front of it and answers some requests itself, with an HTML page instead of the envelope. Check Content-Type before parsing.

    CodeStatusMeaning
    200OKThe request succeeded.
    304Not modifiedYour If-None-Match matched the current ETag. No body is sent.
    400Bad requestA parameter is missing, malformed, or fails validation.
    403ForbiddenThe User-Agent requirement, or a host outside the CDN allowlist.
    404Not foundThe account or resource does not exist.
    413Payload too largeA POST body over 64 KB.
    415Unsupported media typeA POST body that is not application/json.
    429Too many requestsRate limited. Honour Retry-After.
    500Internal server errorAn unexpected failure. The Ray ID identifies it in our logs.
    503Service unavailableSteam is unreachable, rate limiting this server, or a credential is missing.
    504Gateway timeoutSteam did not respond in time.

    Error codes

    error.codeStatusMeaning
    bad_request400The request could not be understood.
    missing_parameter400A required parameter is missing.
    invalid_parameter400A parameter was rejected as invalid.
    invalid_steam_input400Not a SteamID64, vanity name, or profile URL.
    invalid_json400The request body is not valid JSON.
    missing_user_agent403A descriptive User-Agent header is required.
    forbidden_host403That host is not on the Steam CDN allowlist.
    player_not_found404No Steam account matches that identifier.
    not_found404The requested resource does not exist.
    method_not_allowed405That HTTP method is not supported here.
    payload_too_large413The body is larger than the accepted maximum.
    unsupported_media_type415The body must be sent as application/json.
    rate_limited429Rate limit exceeded. Retry after the reset.
    internal_error500An unexpected error occurred.
    not_configured503The server is missing an upstream credential.
    upstream_unavailable503Steam is not responding.
    upstream_rate_limited503Steam rate limited this server.
    upstream_timeout504Steam did not respond in time.

    Errors from the edge

    Cloudflare validates every request against a published schema before it reaches the origin. A path, query string, or JSON body that does not match is rejected there and answered with error 1020 — an HTML page carrying a cf-ray, not the JSON envelope.

    A 1020 is a malformed request rather than an outage, so retrying it changes nothing. The usual causes are an identifier that is not a SteamID64, vanity name, or profile URL; an include value outside the documented set; a limit past its maximum; more than 100 identifiers in one batch; or an unexpected field in a POST body. Correct the request and it goes through. If you are certain it is well-formed, send us the Ray ID.

    Cloudflare codeStatusMeaning
    1020403Rejected by API Shield schema validation, or by a WAF rule.
    1015429Rate limited at the edge. Honour Retry-After before retrying.

    Retrying after 429

    Back off exponentially and honour Retry-After. A tight retry loop keeps the limit active and can get the IP blocked outright. The same backoff suits 502 and 503, which are usually transient. Note that a 429 comes from the edge as an error page, not as the JSON envelope.

    backoff.ts
    /**
     * Rate limiting happens at the edge, so a 429 arrives as an error page rather
     * than the JSON envelope. Read the status before the body.
     */
    export async function call(url: string, init: RequestInit = {}) {
      for (let attempt = 0; attempt < 5; attempt++) {
        const response = await fetch(url, {
          ...init,
          headers: { "User-Agent": "YourAppName/1.0 (contact@example.com)", ...(init.headers ?? {}) }
        });
    
        if (response.status === 429 || response.status >= 500) {
          const retryAfter = Number(response.headers.get("retry-after"));
          const wait = Number.isFinite(retryAfter) && retryAfter > 0
            ? retryAfter * 1000
            : 2 ** attempt * 500;
    
          await new Promise((r) => setTimeout(r, wait));
          continue;
        }
    
        if (!response.headers.get("content-type")?.includes("application/json")) {
          throw new Error(`Expected JSON, got HTTP ${response.status}`);
        }
    
        const body = await response.json();
        if (!body.ok) throw new Error(`${body.error.code}: ${body.error.message}`);
    
        return body.data;
      }
    
      throw new Error("gave up after five attempts");
    }

    All endpoints#

    • GET/api/v2/players/{steamId}Look up an account
    • GETPOST/api/v2/playersBatch lookup
    • GET/api/v2/players/{steamId}/bansBan record only
    • GET/api/v2/players/{steamId}/friendsScan a friend list
    • GET/api/v2/players/{steamId}/statsRust statistics
    • GET/api/v2/players/{steamId}/inventoryRust inventory
    • GET/api/v2/resolveResolve an identifier
    • GET/api/v2/imagesImage proxy
    GET/api/v2/players/{steamId}

    Look up an account#

    Returns the profile, ban record and Rust playtime for one account.

    Takes a SteamID64, a vanity name, or a profile URL. Resolving a name costs an extra Steam call the first time and nothing for the next seven days. include controls how many calls the lookup makes: bans alone is one, the default set is three, and all is four.

    Parameters

    steamIdstringin queryrequired
    In the path. A SteamID64, a vanity name, or a profile URL — percent-encode the URL form. Maximum 256 characters.
    includestringin queryoptional
    Comma-separated sections: profile, bans, rust, level, or all. Defaults to profile,bans,rust. Every section you leave out is one Steam call this request does not make.

    Responses

    • 200Lookup completed.
    • 304Your If-None-Match matched; the body is unchanged and omitted.
    • 400Malformed identifier or an unknown include value.
    • 403The User-Agent header is absent or shorter than 5 characters.
    • 404No Steam account matched.
    • 429Rate limited. Back off before retrying.
    • 503Steam is unavailable.

    Returns one account object.

    Request
    curl "https://rustbanned.net/api/v2/players/76561198012345678" \
      -H "User-Agent: YourAppName/1.0 (contact@example.com)"
    200 · Response
    {
      "ok": true,
      "data": {
        "steamId": "76561198012345678",
        "steamId2": "STEAM_0:0:26039975",
        "steamId3": "[U:1:52079950]",
        "accountId": 52079950,
        "query": "76561198012345678",
        "profile": {
          "personaName": "PlayerName",
          "profileUrl": "https://steamcommunity.com/id/playername/",
          "vanity": "playername",
          "avatar": {
            "small": "https://avatars.steamstatic.com/abc.jpg",
            "medium": "https://avatars.steamstatic.com/abc_medium.jpg",
            "full": "https://avatars.steamstatic.com/abc_full.jpg"
          },
          "visibility": "public",
          "createdAt": "2013-06-14T18:22:41.000Z",
          "createdAtUnix": 1371234161,
          "accountAgeDays": 4464,
          "countryCode": "US",
          "friendListPrivate": false
        },
        "bans": {
          "banned": true,
          "vac": { "banned": true, "count": 1 },
          "game": { "banned": false, "count": 0 },
          "community": { "banned": false },
          "economy": { "status": "none", "banned": false },
          "lastBan": { "daysSince": 412, "estimatedAt": "2024-07-18T00:00:00.000Z" },
          "rustGameBan": false
        },
        "rust": { "hours": 1234.5, "minutes": 74070, "owned": true }
      },
      "meta": {
        "generatedAt": "2026-09-03T12:00:00.000Z",
        "cached": true,
        "ageSeconds": 42,
        "durationMs": 1.71
      }
    }

    The account object#

    Sections are present only when you ask for them. Fields may be added over time; they are never removed or retyped within v2.

    FieldTypeDescription
    steamIdstringThe 64-bit id, as text.
    steamId2stringLegacy form, STEAM_0:0:26039975.
    steamId3stringModern form, [U:1:52079950].
    accountIdnumber32-bit account id.
    querystringThe identifier you sent, echoed back for correlation.
    profile.personaNamestringCurrent display name.
    profile.profileUrlstringSteam community URL.
    profile.vanitystring | nullVanity slug, when the profile uses one.
    profile.avatarobjectsmall, medium and full image URLs.
    profile.visibility"public" | "private"Steam community visibility.
    profile.createdAtstring | nullAccount creation, ISO 8601. Null when hidden.
    profile.createdAtUnixnumber | nullThe same moment as a Unix timestamp.
    profile.accountAgeDaysnumber | nullWhole days since creation.
    profile.countryCodestring | nullTwo-letter country code, when set.
    profile.friendListPrivatebooleanTrue when the friend list cannot be read.
    bans.bannedbooleanTrue when any ban below is active.
    bans.vacobjectbanned, and count of VAC bans.
    bans.gameobjectbanned, and count of game bans.
    bans.communityobjectbanned.
    bans.economyobjectstatus ("none", "probation", "banned") and banned.
    bans.lastBanobject | nulldaysSince and estimatedAt. Null on a clean account.
    bans.rustGameBanboolean | nullTrue when a game ban is attributed to Rust. Null when Steam does not break it down per app.
    rust.hoursnumber | nullPlaytime in hours. Null when the library is hidden.
    rust.minutesnumber | nullThe same playtime in minutes.
    rust.ownedboolean | nullWhether Rust is in the visible library.
    steamLevelnumber | nullSteam level. Only with include=level or all.
    GETPOST/api/v2/players

    Batch lookup#

    Looks up as many as 100 accounts in one request.

    100 is Steam's own per-call maximum, so a full batch costs one profile call and one ban call in total rather than a pair per player. Results keep request order and echo the identifier you sent as query, so they line up with your own list. A bad identifier does not fail the request: it is reported in unresolved (nothing to resolve to) or notFound (resolved, but no such account). Use POST when the list is too long for a URL; the GET form is cacheable and POST is not.

    Parameters

    idsstringin queryrequired
    GET only. Comma-separated identifiers, up to 100. Mixing SteamID64s, vanity names and profile URLs is fine.
    idsstring[]in bodyrequired
    POST only. Same identifiers as an array, up to 100 entries.
    includestringin queryoptional
    Comma-separated sections: profile, bans, rust, level, or all. Defaults to profile,bans,rust. Every section you leave out is one Steam call this request does not make.

    Responses

    • 200Batch completed. Individual identifiers may still have failed to resolve.
    • 400ids is missing, empty, or holds more than 100 entries.
    • 403The User-Agent header is absent or shorter than 5 characters.
    • 413The POST body is larger than 64 KB.
    • 429Rate limited. A batch is charged one unit per ten identifiers.
    • 503Steam is unavailable.

    Returns the accounts that resolved, plus unresolved and notFound lists.

    Request
    curl "https://rustbanned.net/api/v2/players?ids=76561198012345678,playername&include=bans" \
      -H "User-Agent: YourAppName/1.0 (contact@example.com)"
    200 · Response
    {
      "ok": true,
      "data": {
        "players": [
          {
            "steamId": "76561198012345678",
            "query": "76561198012345678",
            "bans": { "banned": true, "vac": { "banned": true, "count": 1 } }
          },
          {
            "steamId": "76561198087654321",
            "query": "playername",
            "bans": { "banned": false, "vac": { "banned": false, "count": 0 } }
          }
        ],
        "unresolved": ["not-a-real-vanity"],
        "notFound": [],
        "requested": 3,
        "returned": 2
      },
      "meta": { "cached": false, "ageSeconds": 0, "durationMs": 118.4 }
    }
    GET/api/v2/players/{steamId}/bans

    Ban record only#

    The ban record on its own, without the profile or playtime.

    One Steam call on a miss, a six-hour shared cache and a five-minute edge cache — the endpoint to use if you are polling. With If-None-Match, an unchanged account returns an empty 304.

    Parameters

    steamIdstringin queryrequired
    In the path. A SteamID64, a vanity name, or a profile URL — percent-encode the URL form. Maximum 256 characters.

    Responses

    • 200Lookup completed.
    • 304Your If-None-Match matched.
    • 400Malformed identifier.
    • 403The User-Agent header is absent or shorter than 5 characters.
    • 404No Steam account matched.
    • 429Rate limited.
    • 503Steam is unavailable.

    Returns the identifiers and the ban object.

    cURL
    curl "https://rustbanned.net/api/v2/players/76561198012345678/bans" \
      -H "User-Agent: YourAppName/1.0 (contact@example.com)"
    200 · Response
    {
      "ok": true,
      "data": {
        "steamId": "76561198012345678",
        "steamId2": "STEAM_0:0:26039975",
        "steamId3": "[U:1:52079950]",
        "accountId": 52079950,
        "bans": {
          "banned": true,
          "vac": { "banned": true, "count": 2 },
          "game": { "banned": true, "count": 1 },
          "community": { "banned": false },
          "economy": { "status": "none", "banned": false },
          "lastBan": { "daysSince": 88, "estimatedAt": "2026-06-07T00:00:00.000Z" },
          "rustGameBan": true
        }
      },
      "meta": { "cached": true, "ageSeconds": 120, "durationMs": 1.9 }
    }
    GET/api/v2/players/{steamId}/friends

    Scan a friend list#

    Checks every account on a player's friend list for bans.

    Returns plain JSON by default. Add stream=true (or send Accept: text/event-stream) and the scan reports progress as it runs, as named Server-Sent Events. A private friend list is not an error: the response is 200 with friendList.private set and an empty array. Friends are returned banned-first, then by Rust playtime. The scan itself costs one call per 25 friends for profiles and one per 25 for bans; playtime is the expensive part, so see includeRust.

    Parameters

    steamIdstringin queryrequired
    In the path. A SteamID64, a vanity name, or a profile URL — percent-encode the URL form. Maximum 256 characters.
    limitintegerin queryoptional
    How many friends to inspect, 1–250. Defaults to 100.
    offsetintegerin queryoptional
    Index into the friend list. Page through a large list with limit and offset; pages never overlap.
    includeRust"all" | "banned" | "none"in queryoptional
    Which friends get Rust playtime. Defaults to all. Steam has no batch form for playtime, so it is one call per friend — "banned" prices only the friends who carry a ban, which is usually a fraction of a list. The legacy true and false are still accepted.
    streambooleanin queryoptional
    Defaults to false. When true the response is an event stream instead of a single JSON body, and is never cached.

    Responses

    • 200Scan completed, or the friend list is private.
    • 400Malformed identifier, or limit/offset out of range.
    • 403The User-Agent header is absent or shorter than 5 characters.
    • 404No Steam account matched.
    • 429Rate limited.
    • 503Steam is unavailable.

    Returns the friend list summary and one entry per friend checked.

    Request
    curl "https://rustbanned.net/api/v2/players/76561198012345678/friends?limit=100" \
      -H "User-Agent: YourAppName/1.0 (contact@example.com)"
    200 · Response
    {
      "ok": true,
      "data": {
        "steamId": "76561198012345678",
        "friendList": { "total": 214, "checked": 100, "banned": 7, "private": false },
        "friends": [
          {
            "steamId": "76561198087654321",
            "steamId2": "STEAM_0:1:63694296",
            "steamId3": "[U:1:127388593]",
            "accountId": 127388593,
            "personaName": "Friend",
            "profileUrl": "https://steamcommunity.com/profiles/76561198087654321/",
            "avatarUrl": "https://avatars.steamstatic.com/def_medium.jpg",
            "friendSince": "2019-02-11T20:43:00.000Z",
            "banned": true,
            "bans": { "banned": true, "game": { "banned": true, "count": 1 } },
            "rust": { "hours": 3120.7, "minutes": 187242, "owned": true }
          }
        ]
      },
      "meta": {
        "cached": false,
        "ageSeconds": 0,
        "durationMs": 942.5,
        "pagination": { "limit": 100, "offset": 0, "total": 214, "returned": 100, "hasMore": true }
      }
    }
    200 · Event stream
    event: open
    data: {"steamId":"76561198012345678"}
    
    event: progress
    data: {"checked":25,"total":100,"friendListTotal":214}
    
    event: progress
    data: {"checked":50,"total":100,"friendListTotal":214}
    
    event: result
    data: {"steamId":"76561198012345678","friendList":{"total":214,"checked":100,"banned":7,"private":false},"friends":[…]}

    Stream events

    Events are named, so switch on the event type rather than sniffing the payload.

    open
    { steamId } — sent immediately, so you can render a spinner.
    progress
    { checked, total, friendListTotal } — emitted every 25 friends.
    result
    The same object the JSON form returns.
    error
    The standard error object. The stream then closes.
    GET/api/v2/players/{steamId}/stats

    Rust statistics#

    Normalised Rust gameplay statistics.

    Combat, survival, building, resources, NPC kills, items and miscellany, with K/D, accuracy and headshot rate already computed. A player who hides game details, or has never played Rust, is not an error: the response is 200 with available: false and a reason, which means that answer caches like any other.

    Parameters

    steamIdstringin queryrequired
    In the path. A SteamID64, a vanity name, or a profile URL — percent-encode the URL form. Maximum 256 characters.
    rawbooleanin queryoptional
    Defaults to false. When true, Steam's raw stat keys are returned alongside the normalised object.

    Responses

    • 200Statistics returned, or reported unavailable with a reason.
    • 400Malformed identifier.
    • 403The User-Agent header is absent or shorter than 5 characters.
    • 404No Steam account matched.
    • 429Rate limited.
    • 503Steam is unavailable.

    Returns the normalised statistics object, or available: false with a reason.

    cURL
    curl "https://rustbanned.net/api/v2/players/76561198012345678/stats" \
      -H "User-Agent: YourAppName/1.0 (contact@example.com)"
    200 · Response
    {
      "ok": true,
      "data": {
        "steamId": "76561198012345678",
        "appId": 252490,
        "available": true,
        "reason": null,
        "stats": {
          "combat": {
            "kills": 4821, "deaths": 3907, "kd": 1.23, "headshots": 1502,
            "headshotRate": 21.4, "bulletsFired": 184203, "bulletsHit": 7018,
            "accuracy": 3.8, "wounded": 812, "woundedAssisted": 260, "beenPickedUp": 143
          },
          "survival": { "deaths": 3907, "suicides": 212, "fallingDeaths": 96, "…": 0 },
          "building": { "structuresBuilt": 21044, "upgradesBuilt": 8123, "repairsDone": 1902 },
          "resources": { "woodHarvested": 4210332, "stoneHarvested": 2810442, "…": 0 },
          "npc": { "scientistsKilled": 1204, "bearsKilled": 96, "…": 0 },
          "items": { "itemsDropped": 8123, "itemsPickedUp": 91224, "itemsCrafted": 1422 },
          "misc": { "barrelsBroken": 18422, "heliHits": 1204, "rocketsFired": 611 }
        },
        "raw": null
      },
      "meta": { "cached": true, "ageSeconds": 60, "durationMs": 2.4 }
    }
    200 · Details hidden
    {
      "ok": true,
      "data": {
        "steamId": "76561198087654321",
        "appId": 252490,
        "available": false,
        "reason": "private",
        "stats": null,
        "raw": null
      },
      "meta": { "cached": true, "ageSeconds": 12, "durationMs": 1.1 }
    }
    GET/api/v2/players/{steamId}/inventory

    Rust inventory#

    Rust skins and items from a player's Steam inventory.

    Each item carries both iconUrl (straight from the Steam CDN) and proxyUrl (the same image through this API, cached and CORS-friendly). A private inventory returns 200 with visibility: "private" and an empty list.

    Parameters

    steamIdstringin queryrequired
    In the path. A SteamID64, a vanity name, or a profile URL — percent-encode the URL form. Maximum 256 characters.
    limitintegerin queryoptional
    Items to return, 1–100. Defaults to 50.

    Responses

    • 200Inventory returned, or reported private.
    • 400Malformed identifier, or limit out of range.
    • 403The User-Agent header is absent or shorter than 5 characters.
    • 404No Steam account matched.
    • 429Rate limited.
    • 503Steam Community is unavailable.

    Returns the inventory summary and up to limit items.

    cURL
    curl "https://rustbanned.net/api/v2/players/76561198012345678/inventory?limit=50" \
      -H "User-Agent: YourAppName/1.0 (contact@example.com)"
    200 · Response
    {
      "ok": true,
      "data": {
        "steamId": "76561198012345678",
        "appId": 252490,
        "visibility": "public",
        "totalItems": 168,
        "items": [
          {
            "assetId": "24175372918",
            "classId": "3111478780",
            "name": "Big Grin",
            "marketHashName": "Big Grin",
            "type": "Facemask Skin",
            "iconUrl": "https://community.cloudflare.steamstatic.com/economy/image/abc/75fx75f",
            "proxyUrl": "/api/v2/images?url=https%3A%2F%2Fcommunity.cloudflare.steamstatic.com%2F…",
            "tradable": true,
            "marketable": true
          }
        ]
      },
      "meta": { "cached": false, "ageSeconds": 0, "durationMs": 210.7 }
    }
    GET/api/v2/resolve

    Resolve an identifier#

    Turns a vanity name or profile URL into a SteamID64.

    Also returns the legacy STEAM_0: and modern [U:1: forms and the 32-bit account id, so you do not have to do the arithmetic. Results are cached for seven days. If you look the same accounts up repeatedly, resolve once and store the SteamID64.

    Parameters

    inputstringin queryrequired
    A SteamID64, vanity name, or profile URL. Maximum 256 characters.

    Responses

    • 200Resolved.
    • 304Your If-None-Match matched.
    • 400Missing or malformed input.
    • 403The User-Agent header is absent or shorter than 5 characters.
    • 404No Steam account matched.
    • 429Rate limited.
    • 503Steam is unavailable.

    Returns every identifier form for the account, and how the input was read.

    cURL
    curl "https://rustbanned.net/api/v2/resolve?input=https%3A%2F%2Fsteamcommunity.com%2Fid%2Fplayername" \
      -H "User-Agent: YourAppName/1.0 (contact@example.com)"
    200 · Response
    {
      "ok": true,
      "data": {
        "query": "https://steamcommunity.com/id/playername",
        "steamId": "76561198012345678",
        "steamId2": "STEAM_0:0:26039975",
        "steamId3": "[U:1:52079950]",
        "accountId": 52079950,
        "source": "vanity_url"
      },
      "meta": { "cached": true, "ageSeconds": 3600, "durationMs": 1.2 }
    }
    GET/api/v2/images

    Image proxy#

    Serves Steam CDN images with long-lived caching and open CORS.

    Item icons are content-addressed and never change, so they are cached for seven days and served immutable. Avatars change often enough to be passed through with a short browser cache instead. Only Steam CDN hosts are accepted; anything else is refused with forbidden_host.

    Parameters

    urlstringin queryrequired
    A percent-encoded Steam CDN image URL, exactly as returned in items[].iconUrl.

    Responses

    • 200The image, with its original content type.
    • 400Missing or unparseable url.
    • 403The host is not a Steam CDN host.
    • 429Rate limited.
    • 503The Steam CDN did not return an image.

    Returns image bytes, not JSON.

    cURL
    curl "https://rustbanned.net/api/v2/images?url=https%3A%2F%2Fcommunity.cloudflare.steamstatic.com%2Feconomy%2Fimage%2Fabc%2F75fx75f" \
      -H "User-Agent: YourAppName/1.0 (contact@example.com)" \
      --output item.jpg

    Watching a roster#

    The common case: an admin watching a list of players for new bans. Batch the roster, ask only for bans, and keep the ETag.

    100 accounts then cost two Steam calls on a cold cache and none on a warm one, and an unchanged roster returns an empty 304. Ban records are cached for six hours, so polling faster than that returns the same answer.

    watch.ts
    /**
     * A ban-watch loop. Storing the ETag means an unchanged roster costs an
     * empty 304 rather than a full payload, on both ends.
     */
    const etags = new Map<string, string>();
    
    async function checkRoster(steamIds: string[]) {
      const url = `https://rustbanned.net/api/v2/players?ids=${steamIds.join(",")}&include=bans`;
      const previous = etags.get(url);
    
      const response = await fetch(url, {
        headers: {
          "User-Agent": "YourAppName/1.0 (contact@example.com)",
          ...(previous ? { "If-None-Match": previous } : {})
        }
      });
    
      if (response.status === 304) return null;   // nothing changed
    
      const tag = response.headers.get("etag");
      if (tag) etags.set(url, tag);
    
      const { data } = await response.json();
      return data.players.filter((p) => p.bans.banned);
    }

    Migrating from v1#

    Optional. v1 is frozen, not deprecated — it stays online and unchanged for everything already built on it. Move when there is something in v2 you want.

    Endpoints

    v1v2Notes
    GET /api/lookup?steamId=…GET /api/v2/players/{steamId}The identifier moves into the path. Add ?include= to trim the response.
    POST /api/lookup {input}GET /api/v2/players/{steamId}Percent-encode a profile URL into the path. POST is only for batches now.
    POST /api/lookup/batch {steamIds}GET or POST /api/v2/playersids replaces steamIds, and the cap rises from 50 to 100.
    POST /api/friends {steamId}GET /api/v2/players/{steamId}/friendsPlain JSON by default; add ?stream=true for progress events.
    GET /api/rust-stats?steamId=…GET /api/v2/players/{steamId}/statsPrivate stats are a 200 with available:false, not a 403.
    GET /api/inventory?steamId=…GET /api/v2/players/{steamId}/inventoryPrivate inventories are a 200 with visibility:"private".
    GET /api/image?url=…GET /api/v2/images?url=…Same allowlist, plus CORS headers and its own rate-limit budget.

    Fields

    v1v2Notes
    { error: string }{ ok: false, error: { code, message, status } }Branch on error.code, never on the message.
    steamIdsteamId, steamId2, steamId3, accountIdAll four forms are returned; no client-side conversion.
    profile.personanameprofile.personaNameEvery field is camelCase.
    profile.timecreatedprofile.createdAt, createdAtUnix, accountAgeDaysISO 8601 alongside the epoch, plus the age in days.
    bans.VACBannedbans.vac.bannedGrouped per ban type, with the count beside it.
    bans.NumberOfVACBansbans.vac.count
    bans.NumberOfGameBansbans.game.countbans.game.banned is the boolean.
    bans.CommunityBannedbans.community.banned
    bans.EconomyBanbans.economy.statusbans.economy.banned is the boolean.
    bans.DaysSinceLastBanbans.lastBan.daysSincelastBan is null on a clean account.
    (none)bans.bannedOne boolean for is this account banned at all.
    (none)bans.rustGameBanTrue when Steam attributes a game ban to Rust.
    rustHoursrust.hoursrust.minutes and rust.owned come with it.
    friendsPrivateprofile.friendListPrivateSame derivation, clearer name.

    Before you ship#

    • Send a real User-Agent

      Name your app and give a contact address. Anything shorter than 5 characters is refused with missing_user_agent.

    • Check ok, not the status code alone

      Every failure carries a stable error.code. Branch on it.

    • Check Content-Type before parsing

      A Cloudflare challenge, a rate limit, or a 1020 block page is HTML. Parsing it as JSON throws something unhelpful.

    • Ask only for what you read

      include=bans is one Steam call; the default set is three. On friend scans, includeRust=banned is the equivalent saving.

    • Batch instead of looping

      One request with 100 ids does the work of 100 requests, and counts as one.

    • Store the ETag

      Send If-None-Match on every poll. Unchanged data then costs an empty 304.

    • Back off on 429

      Honour Retry-After and retry with an exponential delay. A tight retry loop is what turns a rate limit into a block.

    • Handle private as data, not failure

      Private stats, inventories and friend lists are 200 responses with a flag.

    • Log the Ray ID

      Every response carries cf-ray. It is the one identifier that lets us find your request.

    Support

    Email contact@rustbanned.net about blocked requests or unexpected errors. Include your public IP, the User-Agent you sent, the endpoint and method, the HTTP status and error.code, roughly when it happened with the timezone, and the Cloudflare Ray ID from the cf-ray response header — error pages show the same value.