Typing a nickname into a FACEIT stats site and waiting for a page to load gets old fast if you check levels for a whole team, a Discord community, or your own progress every night after ranked queue. FACEIT publishes a real public API, and with about 45 minutes and a Python file you can build a level checker that pulls live Elo and skill level for any player, on demand, without opening a browser. This tutorial walks through the whole build: registering a developer app, calling the v4 API, mapping Elo to the 1-10 level scale, handling the errors that will inevitably show up, and packaging the result as a small project you can actually reuse. By the end you will have a working script, a rate-limit-safe caching layer, and an optional Discord alert that pings your server whenever someone’s level changes.

This isn’t a theoretical exercise. Coaches use exactly this kind of script to pull a roster’s Elo before a scrim without opening ten browser tabs. Community mods use it to gate a Discord role at Level 6 automatically instead of manually checking every applicant. Once you have the core functions working, extending them to either use case is a small step, and the same pattern applies to almost any third-party API that gates data behind a nickname-to-ID lookup, not just FACEIT within the wider esports ecosystem.

Prerequisites: What You Need Before You Start

Keep the list short and you will avoid most of the setup headaches that eat into the actual coding time. Here is exactly what this build assumes, version by version.

  • Python 3.11 or newer (3.10 also works, older versions may hit f-string edge cases)
  • pip, the package manager that ships with Python
  • The requests library, version 2.31.0 or newer
  • A free FACEIT account (you already have one if you play CS2 through FACEIT)
  • A registered app at developers.faceit.com to get an API key
  • A terminal you’re comfortable typing commands into
  • Optional: a Discord server and a webhook URL, for the alert step near the end
  • About 45 minutes, start to finish

Nothing here needs a paid plan. The FACEIT developer portal issues free API keys for personal projects, and the checker you build in this tutorial makes a small enough number of calls that you will not come close to any practical rate ceiling during testing.

Why FACEIT Level Matters: Anti-Cheat, Premium and Matchmaking

Before writing any code, it helps to know what actually sits behind the number your script will fetch. FACEIT level and Elo aren’t just a badge, they gate real parts of the platform. Running FACEIT Anti-Cheat (FACEIT AC) is required to queue for most competitive CS2 matches on the platform, particularly at higher levels, and the client has to stay active before launching CS2 and through the whole match. Players who fail an AC check can be restricted, which affects their ability to keep climbing regardless of raw skill, so a level checker built for a team should treat a sudden Elo freeze as a possible AC or account issue worth flagging, not just a losing streak.

FACEIT Premium, the platform’s paid subscription tier, layers on priority and Premium-only matchmaking queues, deeper access to hubs, leagues and tournaments, and richer match history and stats visibility. Plenty of competitive CS2 players choose Premium over relying solely on Valve’s built-in Premier mode specifically for that queue quality and event access. None of this changes the Elo math your script performs, but it explains why two players at the same level can have very different match histories to show for it, and it’s useful context if you’re building a tool for a community that mixes free and Premium accounts. If you’re also tuning the client itself while you climb, our CS2 console commands setup guide covers the binds and settings most FACEIT regulars configure before queuing.

FACEIT v4 API Endpoints You’ll Use in This Build

It helps to see the handful of endpoints this whole project rests on before diving into the code, so the earlier function names make sense the moment you read them instead of feeling arbitrary. Everything below sits under the base path https://open.faceit.com/data/v4 and requires the Bearer token from Step 1.

EndpointMethodWhat It Returns
/players?nickname={nickname}GETResolves a nickname to a player_id and basic profile data
/players/{player_id}GETFull profile, including per-game Elo and skill_level under the games object
/players/{player_id}/stats/{game_id}GETAggregate match stats (win rate, K/D, matches played) for a given game, e.g. cs2
/players/{player_id}/historyGETRecent match history, useful if you extend the checker to show last-played date

Treat this table as a starting map, not gospel. Third-party API surfaces evolve, and FACEIT’s own developer portal is always the source of truth if a path here stops resolving. The two rows this tutorial actually calls are the player lookup and the full profile fetch, since both the Elo and the skill_level FACEIT computes live inside that profile response.

Step 1: Create a FACEIT Developer App and Get an API Key

Every call to the FACEIT API needs an API key attached as a Bearer token, so this is the step you cannot skip. Sign in to developers.faceit.com with your normal FACEIT account, open the apps dashboard, and create a new application. Give it a name like “level-checker” so you remember what it’s for later, since developer accounts tend to accumulate forgotten test apps over time. Once the app is created, FACEIT issues an API key tied to it. Copy that key somewhere safe. Treat it like a password: it goes in an environment variable, never in a script you might commit to a public repo.

Set the key as an environment variable so your code never hardcodes it:

export FACEIT_API_KEY="your-api-key-here"

# On Windows PowerShell instead:
# $env:FACEIT_API_KEY="your-api-key-here"

Test that the key actually works before writing any Python. A quick curl call against the players search endpoint confirms authentication is set up correctly:

curl -s "https://open.faceit.com/data/v4/players?nickname=s1mple" \
  -H "Authorization: Bearer $FACEIT_API_KEY"

A working key returns a JSON object with player details. A 401 response means the key is missing or wrong, and a 404 means the nickname doesn’t exist or was typed incorrectly. Both are worth ruling out now, before they show up buried inside your Python traceback later.

Step 2: Set Up Your Python Environment

A virtual environment keeps this project’s dependencies separate from anything else on your machine, which matters more than it sounds once you have a few Python projects competing for the same package versions. Create a project folder, then a venv inside it.

mkdir faceit-level-checker
cd faceit-level-checker
python3 -m venv venv

# Activate it:
source venv/bin/activate        # macOS/Linux
# venv\Scripts\activate         # Windows

pip install requests==2.31.0

You’ll know the environment is active when your terminal prompt shows (venv) at the start of the line. If it doesn’t show up, the activation command didn’t run in the shell you’re typing in, which is the single most common reason a “working” script suddenly throws ModuleNotFoundError: No module named 'requests' on a different terminal tab.

Step 3: Resolve a Player’s FACEIT ID From Their Nickname

The API doesn’t let you jump straight from a nickname to CS2 stats. You first resolve the nickname to a player_id, a UUID FACEIT assigns internally, then use that ID for every stats call afterward. This two-step pattern shows up across most FACEIT tooling built around the v4 API, and skipping it is the source of a lot of confused Stack Overflow questions from developers expecting a single combined endpoint.

import os
import requests

API_KEY = os.environ["FACEIT_API_KEY"]
BASE_URL = "https://open.faceit.com/data/v4"
HEADERS = {"Authorization": f"Bearer {API_KEY}"}


def get_player_id(nickname):
    resp = requests.get(
        f"{BASE_URL}/players",
        headers=HEADERS,
        params={"nickname": nickname},
        timeout=10,
    )
    resp.raise_for_status()
    data = resp.json()
    return data["player_id"]

The raise_for_status() call matters here. Without it, a 404 for a mistyped nickname returns silently and your script fails several lines later with a confusing KeyError instead of a clear “player not found” message.

Step 4: Fetch CS2 Elo and Stats From the v4 API

With a player_id in hand, the next call pulls the actual CS2 stats, including current Elo, from the player’s stats endpoint scoped to the game. This is the payload your level checker actually cares about.

def get_cs2_stats(player_id):
    resp = requests.get(
        f"{BASE_URL}/players/{player_id}/stats/cs2",
        headers=HEADERS,
        timeout=10,
    )
    resp.raise_for_status()
    return resp.json()


def get_current_elo(player_id):
    resp = requests.get(
        f"{BASE_URL}/players/{player_id}",
        headers=HEADERS,
        timeout=10,
    )
    resp.raise_for_status()
    data = resp.json()
    games = data.get("games", {})
    cs2 = games.get("cs2", {})
    return cs2.get("faceit_elo")

Note the split between two endpoints: the player profile call returns the live Elo and skill_level FACEIT currently assigns, while the stats endpoint returns match history aggregates like win rate and K/D. Most level-checker tools only need the first one, but pulling both lets you show a fuller picture if you’re building something more like a mini stats card. Endpoint paths on third-party APIs shift occasionally, so if a call starts returning 404s that used to work, check the current reference at developers.faceit.com before assuming your code broke.

It helps to see the actual shape of the response before you write code against it. A trimmed example of what the player profile endpoint returns looks roughly like this, with the Elo and skill level nested under the game key:

{
  "player_id": "f4f10df8-8b4e-4c1c-8dcb-example0000",
  "nickname": "example_player",
  "country": "us",
  "games": {
    "cs2": {
      "region": "EU",
      "game_player_id": "example0000",
      "skill_level": 6,
      "faceit_elo": 1284
    }
  }
}

That nested structure is exactly why the get_current_elo function above walks through games then cs2 before reaching faceit_elo. Miss a level of nesting and you’ll get a KeyError or a silent None instead of the number you expect, which is a common source of confusion the first time someone inspects this API’s response shape.

Step 5: Map Elo to FACEIT Level 1-10

FACEIT already returns a skill_level field alongside Elo in most cases, but building your own mapping is worth doing anyway. It lets you show “how far to the next level” progress, and it protects your tool if a field ever comes back empty. The table below reflects the CS2 Elo bands most 2026 rank guides converge on, with Level 1 starting at 100 Elo and Level 10 opening at 2,001 Elo with no upper ceiling.

FACEIT LevelElo RangeTypical Tier
Level 1100 – 500Beginner
Level 2501 – 750Beginner
Level 3751 – 900Low intermediate
Level 4901 – 1,050Intermediate (default new-account start, ~1,000 Elo)
Level 51,051 – 1,200Intermediate
Level 61,201 – 1,350Above average
Level 71,351 – 1,530Above average
Level 81,531 – 1,750Advanced
Level 91,751 – 2,000Advanced
Level 102,001+Elite, top 1-2% of the CS2 player base

Worth flagging: FACEIT’s own older support documentation lists a wider Level 1 band of 100 to 800 Elo, a legacy range that predates the tighter CS2-specific bands most current guides use. If your script’s output looks off by one level compared to what a player sees in the FACEIT client, this discrepancy between legacy and current bands is usually why, not a bug in your code. Here’s the mapping function:

ELO_LEVEL_BANDS = [
    (100, 500, 1),
    (501, 750, 2),
    (751, 900, 3),
    (901, 1050, 4),
    (1051, 1200, 5),
    (1201, 1350, 6),
    (1351, 1530, 7),
    (1531, 1750, 8),
    (1751, 2000, 9),
    (2001, float("inf"), 10),
]


def elo_to_level(elo):
    for low, high, level in ELO_LEVEL_BANDS:
        if low <= elo <= high:
            return level
    return None

Step 6: Assemble the Full Level-Checker Script

With the pieces from steps 3 through 5 written, wiring them into a runnable command-line tool takes just a small amount of glue code and an argparse block so you can call it with a nickname straight from the terminal.

import argparse


def check_level(nickname):
    player_id = get_player_id(nickname)
    elo = get_current_elo(player_id)
    if elo is None:
        print(f"{nickname} has no recorded CS2 Elo yet.")
        return
    level = elo_to_level(elo)
    print(f"{nickname}: Level {level} ({elo} Elo)")


if __name__ == "__main__":
    parser = argparse.ArgumentParser(description="Check a FACEIT CS2 level.")
    parser.add_argument("nickname", help="FACEIT nickname to look up")
    args = parser.parse_args()
    check_level(args.nickname)

Save all of it, including the earlier functions, into a single file named checker.py. Run it with python checker.py s1mple (swap in any real nickname) and you should see a level and Elo printed straight to your terminal within a second or two.

Step 7: Handle Errors, Auth Failures and Edge Cases

A script that only works when everything goes right isn't a tool, it's a demo. Wrap the API calls so a bad nickname, an expired key, or a rate limit produces a message someone can actually act on instead of a raw Python traceback.

def check_level(nickname):
    try:
        player_id = get_player_id(nickname)
    except requests.exceptions.HTTPError as e:
        status = e.response.status_code
        if status == 404:
            print(f"No FACEIT player found for nickname '{nickname}'.")
        elif status == 401:
            print("API key rejected. Check FACEIT_API_KEY and try again.")
        elif status == 429:
            print("Rate limited by the FACEIT API. Wait a moment and retry.")
        else:
            print(f"Unexpected error ({status}) while resolving player.")
        return

    try:
        elo = get_current_elo(player_id)
    except requests.exceptions.RequestException as e:
        print(f"Failed to fetch stats: {e}")
        return

    if elo is None:
        print(f"{nickname} has no recorded CS2 Elo yet.")
        return

    print(f"{nickname}: Level {elo_to_level(elo)} ({elo} Elo)")

Catching requests.exceptions.HTTPError specifically for the player lookup, and the broader RequestException for the stats call, covers both API-level errors (bad status codes) and network-level ones (timeouts, DNS failures, dropped connections). Both categories happen in the real world, often at the worst possible moment, like mid-demo in front of a client.

Step 8: Add Caching to Respect Rate Limits

The FACEIT API enforces per-key rate limits, and the exact ceiling depends on your app's tier in the developer portal. Rather than guessing at a specific number, build the assumption into your tool from day one: cache results for a short window so re-checking the same player twice in a minute doesn't burn an API call each time. A simple in-memory TTL cache handles this without adding a database dependency.

import time

_cache = {}
CACHE_TTL_SECONDS = 300  # 5 minutes


def get_current_elo_cached(player_id):
    now = time.time()
    if player_id in _cache:
        elo, timestamp = _cache[player_id]
        if now - timestamp < CACHE_TTL_SECONDS:
            return elo
    elo = get_current_elo(player_id)
    _cache[player_id] = (elo, now)
    return elo

Five minutes is a reasonable default for a personal tool. If you're checking dozens of players in a loop, for a team roster or a Discord bot command, bump the TTL to 10-15 minutes since FACEIT Elo doesn't move between individual API calls made seconds apart anyway.

Step 9: Test the Tool End to End

Run the finished script against a few known nicknames, including at least one you're confident is high-level and one you expect to be lower, to sanity-check the Elo-to-level mapping against what those players actually show in the FACEIT client. A typical successful run looks like this:

$ python checker.py your_nickname
your_nickname: Level 6 (1284 Elo)

$ python checker.py made_up_name_xyz
No FACEIT player found for nickname 'made_up_name_xyz'.

$ python checker.py brand_new_account
brand_new_account has no recorded CS2 Elo yet.

If the level looks wrong by exactly one band, double-check whether you're comparing against the legacy 100-800 Level 1 range mentioned in Step 5. If Elo comes back as None for an account you know has played matches, the most likely cause is a private profile setting on FACEIT restricting stats visibility to the account owner.

Also test a nickname with non-ASCII characters, accented letters or a mix of scripts show up constantly on FACEIT given how global the platform's player base is. The params argument in the requests calls already handles URL encoding for you, so a nickname like "Émigré_123" should resolve without any extra work on your end. If it doesn't, the issue is almost always a copy-paste error introducing an invisible character rather than an actual encoding bug, so retype the nickname manually before assuming your code is broken.

Step 10: Package It as a Complete Working Project

A single script is fine for testing, but a small amount of structure makes the project reusable and shareable. Organize the final files like this:

faceit-level-checker/
├── checker.py
├── requirements.txt
├── .env.example
└── README.md
# requirements.txt
requests==2.31.0

Put FACEIT_API_KEY= in a .env.example file (never commit the real key), and load it with python-dotenv if you want the key read automatically instead of exported by hand each session. Anyone who clones the project can then get running with three commands: create a venv, pip install -r requirements.txt, and drop their own key into a local .env file.

Keeping Your API Key Secure

An API key is a credential, and it deserves the same handling as a password. Beyond keeping it out of source control, add a .gitignore entry for your real .env file before you make your first commit, not after, since a key pushed to a public GitHub repo can be scraped by bots within minutes even if you delete the commit later. If you eventually wrap this checker in a web app, never expose the FACEIT API key to the browser. Client-side JavaScript that calls the FACEIT API directly with an embedded key hands that key to anyone who opens the browser's developer tools. Route the call through your own backend instead, so the key stays server-side and the browser only ever talks to your server.

If a key does leak, revoke it immediately from the developer app dashboard and issue a new one. Rotating an app's key doesn't require creating an entirely new app, so there's no reason to delay once you suspect exposure.

Teams sharing a single checker across multiple people should avoid passing the raw key around in chat messages, even privately. A better pattern is running the script on one shared machine or a small internal server that team members interact with indirectly, whether through a Discord command, a scheduled report, or a simple internal web page, so the key itself only ever lives in one place instead of scattered across everyone's individual environment files.

Step 11 (Advanced): Send a Discord Alert When a Level Changes

If you're checking a roster of teammates regularly, a Discord webhook that fires automatically when someone climbs (or drops) a level is more useful than a script you have to remember to run manually. Discord webhooks accept a simple JSON POST, no bot framework required.

DISCORD_WEBHOOK_URL = os.environ.get("DISCORD_WEBHOOK_URL")


def notify_discord(nickname, old_level, new_level, elo):
    if not DISCORD_WEBHOOK_URL:
        return
    direction = "climbed to" if new_level > old_level else "dropped to"
    content = f"{nickname} {direction} Level {new_level} ({elo} Elo)."
    requests.post(DISCORD_WEBHOOK_URL, json={"content": content}, timeout=10)

Wire this in by storing each player's last-seen level (a small JSON file or SQLite table works fine for a handful of players), comparing it against the freshly fetched level on each run, and calling notify_discord only when the two differ.

Step 12 (Advanced): Automate Checks With a Scheduled Job

Running the script manually defeats the point of building an alert system. A cron job on Linux or macOS (or Task Scheduler on Windows) can run the checker every hour without you touching a keyboard.

# crontab -e, then add this line to run hourly:
0 * * * * cd /path/to/faceit-level-checker && venv/bin/python checker.py your_nickname >> log.txt 2>&1

The full path to the venv's Python interpreter matters here. Cron runs with a minimal environment that doesn't know about your shell's activated virtual environment, so calling plain python instead of venv/bin/python is a common reason "it works when I run it myself but not on schedule."

Common Pitfalls When Building a FACEIT Level Checker

Most of the bugs you'll hit building this aren't exotic. They're the same handful of mistakes that show up in almost every project wrapping a third-party API, and knowing them ahead of time saves an evening of debugging.

  • Hardcoding the API key directly in checker.py instead of pulling it from an environment variable, which turns into a leaked credential the moment the file reaches a public repo.
  • Assuming the game parameter is "csgo" for CS2 stats. FACEIT split the two titles, and CS2-specific data lives under the cs2 game key, not the older csgo one.
  • Skipping raise_for_status(), which turns clear API errors (404, 401, 429) into confusing downstream KeyErrors several lines later.
  • Polling the API in a tight loop with no caching or delay, which burns through your rate limit fast when checking more than a handful of players.
  • Treating FACEIT level and CS2 Premier rating as the same number. They're separate systems on separate scales, and mapping one to the other is only ever approximate.
  • Forgetting that nicknames are case-sensitive in some lookups and that special characters need URL encoding when passed as query parameters.
  • Not handling accounts with zero recorded matches, which return a stats object with missing or null Elo fields instead of an error.

Troubleshooting: 8 Common Errors and How to Fix Them

When something breaks, the fastest path to a fix is matching the exact symptom you're seeing against the table below rather than re-reading every step from the top. Most of these come down to three root causes: an environment variable not making it into the process that needs it, a nickname or field name that doesn't match what the API actually expects, or a rate limit that a short cache would have avoided entirely.

SymptomLikely CauseFix
401 Unauthorized on every callMissing or malformed API keyConfirm FACEIT_API_KEY is exported in the same shell running the script, and that the header reads "Bearer <key>" with no extra quotes
404 on player lookupNickname typo or player never registeredVerify the exact nickname on faceit.com, capitalization included
429 Too Many RequestsRate limit hit from polling too fastAdd or extend the TTL cache from Step 8, and add a short delay between bulk lookups
Elo comes back as nullPrivate profile or zero CS2 matches playedHandle the None case explicitly instead of assuming a number
ModuleNotFoundError: requestsVirtual environment not activatedRe-run source venv/bin/activate before executing the script
SSL certificate verify failedCorporate proxy or outdated CA bundleUpdate certifi via pip, or run on a network without proxy interception
Script always reports Level 4Reading the wrong JSON field, or the account is genuinely new (default start is close to 1,000 Elo)Print the raw Elo value first to confirm what the API actually returned
Cron job never runsWrong interpreter path or missing environment variables in cron's contextUse the venv's full python path and export FACEIT_API_KEY inside the crontab entry or a sourced .env file

The trickiest of these to diagnose is usually the cron failure, because the script works perfectly when you run it by hand and that's exactly the environment cron doesn't replicate. Print os.environ to a log file as the very first line of the script while debugging a scheduled run, since a missing variable shows up immediately that way instead of after minutes of guessing. The private-profile Elo issue is the second most common report from people who've built a version of this tool for a whole team, since not every player realizes their FACEIT privacy settings can hide stats from third-party API calls even while the profile page itself stays visible in a browser.

Advanced Tips for Scaling Beyond One Player

Checking a single nickname is the easy case. A five-person team roster, a Discord community leaderboard, or a tournament organizer tracking 40 players all need the same core logic run at scale, and a few adjustments make that practical without hammering the API.

Batch lookups benefit from swapping the synchronous requests calls for an async HTTP client so multiple players resolve concurrently instead of one after another. Storing historical Elo readings in a lightweight SQLite file, rather than just the latest value, unlocks trend charts showing whether a player is climbing or plateauing over weeks, which is far more useful to a coach than a single snapshot. If you're running this across a large roster, consider registering a second developer app for a separate API key so you can round-robin requests and stay well under any single key's ceiling. Finally, wrap the whole thing behind a small Flask or FastAPI endpoint if you want teammates to check their own level through a shared link instead of running Python locally.

A streaming-focused variant is worth mentioning too. Plenty of CS2 casters and content creators want a live Elo readout on an OBS overlay during a match rather than a terminal window. The same get_current_elo function feeds that use case directly: write the result to a small local JSON or text file on a timer, then point an OBS text source at that file so it refreshes automatically without touching the stream layout. Keep the polling interval generous, once every 60 to 120 seconds is plenty, since Elo only changes at the end of a match and there's no benefit to checking more often than that during a live broadcast. If you'd rather adapt this project into a full rank tracker with a UI instead of a CLI script, our Fortnite Rank Tracker Setup guide walks through the dashboard side of that pattern, and our CS2 Ranks Explained tracker tutorial covers the equivalent build for Valve's own in-game rank system.

One more thing worth building in early: a simple retry with backoff around the request calls. A single dropped connection or a momentary 500 from FACEIT's side shouldn't crash a scheduled job that's otherwise running fine. Wrapping the two lookup functions in a small retry loop, three attempts with an increasing delay between them, turns an occasional network blip into a non-event instead of a gap in your logs.

Region is another detail worth handling explicitly if your community spans more than one continent. The player profile response includes a region field alongside Elo, since FACEIT groups matchmaking pools geographically (EU, NA, SA and others) for latency reasons even though the Elo scale and level bands are the same everywhere. A checker built for an international team should surface that region field next to the level, so a coach comparing two players immediately understands they're queuing against different opponent pools rather than assuming the numbers are directly comparable in every respect.

For further reference on the underlying tools, the Python requests library documentation covers timeout and retry behavior in more depth than fits here, and MDN's Fetch API reference is the equivalent starting point if you'd rather build the same checker in JavaScript for a browser-based version. FACEIT's own GitHub presence lists some of the platform's open tooling as a secondary reference point. For background on how the Elo bands and rank distribution break down across the player base, the CS2 FACEIT guide from CS2pedia and the Elo explainer from Fgrind both cover the scoring system from the player's side, while Esports Tales publishes periodic rank-distribution breakdowns showing what share of players sit at each level.

How FACEIT Level Compares to CS2 Premier Rating

Players moving between FACEIT and Valve's own CS2 Premier mode often want a rough sense of how the two scales line up, since neither uses the other's numbers directly. The mapping below is approximate, built from comparisons published across current CS2 rank guides, and shouldn't be treated as an exact conversion since the two systems weigh performance differently. For a deeper breakdown of how the two ranking systems compare on cost and structure, see our CS2 Premier Rank vs FACEIT Level comparison, and for how FACEIT stacks up against ESEA specifically, our CS2 Ranks vs ESEA vs FACEIT breakdown covers player-base size on each platform.

CS2 Premier RatingFACEIT LevelApproximate FACEIT Elo
0 - 4,999 (Grey)Level 1 - 2100 - 750
5,000 - 9,999Level 3 - 4751 - 1,050
10,000 - 14,999Level 5 - 61,051 - 1,350
15,000 - 19,999Level 7 - 81,351 - 1,750
20,000 - 24,999 (Pink)Level 9 - 101,751 - 2,300

If you want your checker tool to display both numbers side by side, note that no public API currently exposes CS2 Premier rating the way FACEIT exposes Elo, so that half of the comparison has to come from a screenshot or manual entry rather than an automated call.

Frequently Asked Questions

How do I find my own FACEIT player ID without writing code?
Open your profile on faceit.com and check the URL. Some third-party lookup sites also display the player_id alongside your nickname if you search for yourself directly.

Does the FACEIT API cost anything to use?
The developer portal issues free API keys for standard use. FACEIT has not published a paid tier specifically for the public data API as of this writing, though that's worth confirming on developers.faceit.com since terms can change.

Why does my script show a different level than the FACEIT website?
The most common cause is comparing against the older 100-800 Level 1 range still listed in some legacy FACEIT support articles instead of the tighter current bands used in this guide. A brief caching delay can also mean you're looking at Elo from a few minutes before someone's last match finished.

Can this same approach check levels for games other than CS2?
Yes. Swap the game key in the stats endpoint call from cs2 to whichever game ID FACEIT uses for that title, and adjust the Elo bands, since level thresholds aren't guaranteed to be identical across every game on the platform.

How often can I safely poll the API without getting rate-limited?
FACEIT enforces per-key rate limits, but the exact ceiling depends on your app's registered tier and isn't published as a single fixed number. Build in caching from the start (Step 8) rather than trying to find the limit by triggering 429 errors. If you're checking more than 10 or 15 players in a single run, add a small delay (even 200-300 milliseconds) between each request rather than firing them all at once, since a burst of near-simultaneous calls is more likely to trip a limit than the same number of calls spread over a few seconds.

Is it against FACEIT's terms to build a level-checker bot?
Reading public stats through the official API for personal or community use, without scraping the website or bypassing authentication, is exactly what the developer API exists for. Review the current terms on developers.faceit.com before deploying anything at scale or monetizing it, since usage policies can be updated independent of this guide.

What's the difference between FACEIT level and CS2 Premier rank?
FACEIT level is a 1-10 scale tied to FACEIT's own Elo system on FACEIT's third-party matchmaking. CS2 Premier rating is Valve's separate in-game ranking system. They track similar skill but run on independent scales, so any mapping between them (like the table above) is an approximation, not an official conversion. A player grinding almost exclusively on FACEIT can have a well-established level while their Premier rating lags behind from inactivity, and the reverse happens just as often.

My cached Elo value looks stale. How do I force a fresh check?
Clear the in-memory cache by restarting the script, or lower CACHE_TTL_SECONDS temporarily. For the file-based or database version used in the automation steps, delete the stored record for that player_id before the next scheduled run.