Skip to main content

Universal Game API (gameapi)

One interface. Every game. No API archaeology.

gameapi is a Python library that unifies public game APIs behind a single, consistent interface. Instead of learning a new client for every game, you write the same code for Chess.com, Lichess, and whatever comes next.

PyPI

Go to PyPI and write an email to be invited as a collaborator.

Installation

pip install gameapi
from gameapi import GameAPI

with GameAPI() as api:
    player = api.player(game="chess_com", identifier="hikaru")
    print(player.name, player.rank.rating)   # hikaru 2800

Why

Every game API looks different:

# Without gameapi
chess_client.get_profile(...)
rl_client.fetch_stats(...)
mc_client.player_lookup(...)

gameapi gives you one shape instead:

# With gameapi
api.player(game="chess_com", identifier="...")
api.player(game="lichess", identifier="...")
api.player(game="rocket_league", identifier="...")  # once implemented

Common fields (name, stats, rank) are normalized. Anything that doesn't generalize lives on player.game_data.


Supported Games

Game Slug Auth Required Data Source
Chess.com chess_com No Chess.com Published-Data API
Lichess lichess No Lichess Public API
osu! osu Yes (OAuth) osu! API v2
from gameapi import supported_games
print(supported_games())  # ['chess_com', 'lichess', 'osu']

osu!

osu! is the first integration that requires credentials. Create a free OAuth application at https://osu.ppy.sh/home/account/edit ("New OAuth Application" — no redirect URI needed) and pass the Client ID/Secret as a single api_key string in the form "<client_id>:<client_secret>":

from gameapi import GameAPI

with GameAPI(api_key="12345:your-client-secret") as api:
    player = api.player(game="osu", identifier="mrekk")
    print(player.name, player.rank.rating, player.rank.position)  # mrekk 18000.5 1

gameapi handles the OAuth client-credentials token exchange, caching, and refresh for you — you only ever deal in the two credentials above.

osu! has no head-to-head "match" concept like chess does, so api.matches(game="osu", identifier=...) returns the player's recent play history instead, with result set to "win" for passed plays and "loss" for failed ones. leaderboard(game="osu", region="US") accepts an optional ISO country code to get a country's performance rankings instead of the global one.


Installation

pip install gameapi

Requires Python 3.9+.

Development

git clone https://github.com/F0xyN0xy/universal-game-api.git
cd universal-game-api
pip install -e ".[dev]"
pytest

Quick Start

Player Profile

from gameapi import GameAPI

with GameAPI() as api:
    player = api.player(game="chess_com", identifier="hikaru")
    print(player.name)           # hikaru
    print(player.rank.tier)      # GM
    print(player.rank.rating)    # 2800
    print(player.stats)          # PlayerStats(games_played=..., wins=...)

Recent Matches

for match in api.matches(game="chess_com", identifier="hikaru", limit=10):
    print(match.result, match.opponent, match.played_at)

Leaderboard

board = api.leaderboard(game="chess_com")
for entry in board.top(5):
    print(entry.position, entry.name, entry.rating)

Batch Lookups

players = api.compare_players("chess_com", ["hikaru", "magnuscarlsen", "nihalsarin"])
for p in players:
    print(p.name, p.rank.rating)

Async

import asyncio
from gameapi import AsyncGameAPI

async def main():
    async with AsyncGameAPI() as api:
        player = await api.player(game="lichess", identifier="drnykterstein")
        print(player.name)

asyncio.run(main())

Caching

Optional in-process caching reduces redundant requests:

api = GameAPI(cache=True, cache_ttl=60)  # seconds

Nothing sensitive is ever cached — only parsed response data.


Rate Limits & Retries

gameapi retries transient failures (HTTP 429/500/502/503/504) with exponential backoff, then raises typed exceptions:

from gameapi import RateLimitError

try:
    player = api.player(game="chess_com", identifier="hikaru")
except RateLimitError as e:
    print(f"Rate limited, retry after {e.retry_after}s")

429 responses respect the upstream Retry-After header when provided.


Error Handling

All exceptions inherit from GameAPIError:

from gameapi import (
    GameAPIError,
    GameNotSupportedError,
    PlayerNotFoundError,
    AuthenticationError,
    RateLimitError,
    APIUnavailableError,
    InvalidResponseError,
)

API Reference

GameAPI(api_key=None, cache=False, cache_ttl=60.0, timeout=10.0, max_retries=2)

Method Returns
player(game, identifier) Player
matches(game, identifier, limit=20) list[Match]
leaderboard(game, region=None) Leaderboard
compare_players(game, identifiers) list[Player]
game_info(game) dict
close() —

Context-manager compatible: with GameAPI() as api:

AsyncGameAPI has identical signatures, await-ed.

Models

  • Player — name, game, identifier, stats, rank, game_data, avatar_url
  • PlayerStats — games_played, wins, losses, draws, win_rate
  • Rank — tier, rating, position, raw
  • Match — id, game, played_at, result, opponent, game_data
  • Leaderboard / LeaderboardEntry — position, name, rating

Every model is a @dataclass, so dataclasses.asdict(player) works out of the box.


Demo Project

A small CLI and dashboard built on gameapi lives in demo-project/:

cd demo-project
pip install -e "."

# Look up a player
python -m demo_project chess_com hikaru -m 5 -l

# Compare two players side-by-side
python src/demo_project/dashboard.py chess_com hikaru magnuscarlsen

Contributing

See CONTRIBUTING.md for how to add a new game integration. The pattern is:

  1. Create src/gameapi/games/<game>/
  2. Subclass GameIntegration
  3. Register it in games/registry.py

No changes to client.py or async_client.py are needed.


License

MIT

Legal

gameapi only integrates with public APIs that permit this kind of access under their terms of service. It does not scrape websites in ways that violate their terms, and does not attempt to bypass authentication, rate limits, or anti-bot protections.

Metadata

Release files for universal-game-api 0.2.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for universal-game-api 0.2.0
File Size Uploaded
universal_game_api-0.2.0.tar.gz 28.3 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for universal-game-api 0.2.0
File Interpreter ABI Platform
universal_game_api-0.2.0-py3-none-any.whl Python 3 none any Details

Total release size: 56.7 kB

Release files / universal_game_api-0.2.0.tar.gz

Download URL universal_game_api-0.2.0.tar.gz
Size 28.3 kB
Tags Source
SHA-256 checksum
How to use checksums
744bf8517c7284aa3716d6e3e713fc6a955cb79333546d26ce771834617a54c3
BLAKE2b-256 checksum
How to use checksums
88e299a98ea8fc9193483d0218c82893cb52493678381a17b2b941a716a76933
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.12.0

Release files / universal_game_api-0.2.0-py3-none-any.whl

Download URL universal_game_api-0.2.0-py3-none-any.whl
Size 28.4 kB
Tags Python 3
SHA-256 checksum
How to use checksums
0bdf735adcc5ec324edb75abcb1ecb21403b7b036426a59e7f9f79620f13f29a
BLAKE2b-256 checksum
How to use checksums
b62f27caa9f3d67ef502ee21b1b25e378410443bb86d0f896fc41822a814e019
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.12.0

Release history Release notifications | RSS feed

0.2.1

2 release files

This release

0.2.0 This release

2 release files

0.1.0

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page