OverGraphQL API exposes Overwatch 2 heroes, roles, gamemodes, maps and player statistics through a single GraphQL endpoint. It's a pure facade over OverFast API: it never talks to Blizzard directly and doesn't parse anything itself β it reshapes OverFast REST data into one relational graph, so you can fetch exactly the data you need in a single query. Built with Strawberry GraphQL and httpx.
- π How it works
- π Architecture
- π Example queries
- π Run for production
- π½ Run as developer
- βοΈ Settings
- π‘οΈ Guardrails
- π Credits
- π License
flowchart LR
Client["GraphQL client"] -->|"POST /graphql"| OverGraphQL["OverGraphQL API"]
OverGraphQL -->|REST| OverFast["OverFast API"]
OverFast -->|scraping| Blizzard["Blizzard pages"]
Two kinds of data, two strategies:
- Semi-static data (heroes, roles, gamemodes, maps) is cached in-process for 24 hours. Concurrent fetches of the same resource are coalesced, and upstream calls are paced below OverFast's rate limit, so even a cold-cache
heroesquery (one detail call per hero) stays friendly to the upstream. - Player data is always fetched from OverFast, which owns freshness through its own Stale-While-Revalidate cache. One player at a time, no batching.
flowchart TD
R["Field resolver"] --> C{"In cache?"}
C -->|hit| OK["Return"]
C -->|miss| L["Coalescing lock<br/>(one fetch per key)"]
L --> P["Paced GET<br/>(< OverFast rate limit,<br/>retry once on 429)"]
P --> D["Parse to domain model"]
D --> S["Cache (24h TTL)"] --> OK
R2["Player field resolver"] --> P2["Paced GET"] --> D2["Parse to domain model"] --> OK2["Return<br/>(freshness owned by OverFast SWR)"]
The schema is fully documented: every type, field and argument carries a description, browsable in the GraphiQL explorer served at /graphql (the GraphQL equivalent of OverFast's Redoc/Swagger).
Hexagonal-lite, dependencies flow inward only:
flowchart LR
subgraph GraphQL layer
Q["Query / types<br/>(strawberry)"]
end
subgraph Domain
M["Models<br/>(plain dataclasses)"]
PT["OverFastPort<br/>(Protocol)"]
end
subgraph Adapters
A["OverFastClient<br/>(httpx + TTL cache)"]
end
Q --> PT
Q --> M
A -. implements .-> PT
A --> M
- Domain β plain frozen dataclasses and a single
typing.Protocolport; no framework imports. Most domain models are registered directly as strawberry types (zero mapping code). - Adapters β
OverFastClientimplements the port: HTTP, caching, coalescing, pacing, parsing. - GraphQL layer β resolvers only see the port, injected through the request context; tests swap in an in-memory fake.
Hero, map and gamemode keys are plain strings on purpose: new Blizzard content flows through without a schema update. Only closed sets (roles, platforms, player gamemodes) are enums.
The single endpoint is POST /graphql. Opening it in a browser serves GraphiQL, with autocompletion and the full schema documentation.
Static data is one relational graph β here heroes with their role, and maps with their gamemodes, in one query:
query StaticData {
heroes(key: "ana") {
name
description
role {
name
description
}
abilities {
name
description
}
}
maps {
name
location
gamemodes {
name
}
}
}Every list query (roles, gamemodes, maps, heroes) accepts an optional key filter; an unknown key returns an empty list.
Player profile and statistics β select only what you need, each stats field triggers its own upstream fetch:
query PlayerProfileAndStats {
player(playerId: "TeKrop-2217") {
avatar
namecard
title
username
lastUpdatedAt
statsSummary {
general {
kda
timePlayed
winrate
gamesPlayed
total {
healing
damage
assists
}
}
}
}
}Career statistics with labels, per platform and gamemode:
query CareerStats {
player(playerId: "TeKrop-2217") {
careerStats(platform: PC, gamemode: COMPETITIVE) {
hero
categories {
label
stats {
label
value
}
}
}
}
}An unknown player returns player: null.
Ensure you have docker and docker compose installed, then:
docker compose up -d --buildThe API listens on http://localhost:8080/graphql. No mandatory configuration: by default it targets the live OverFast API instance. Create a .env file to override any setting.
Requirements: docker, docker compose and just.
just build # build Docker images (required first)
just start # run the app with autoreload on localhost:8000
just test # run tests with coverage
just lint # ruff linter
just format # ruff formatter
just check # ty type checker
just lock # update uv.lockAll settings are environment variables (or a .env file), loaded by pydantic-settings:
| Variable | Default | Description |
|---|---|---|
OVERFAST_API_URL |
https://overfast-api.tekrop.fr |
Base URL of the OverFast API instance used as upstream |
STATIC_DATA_TTL |
86400 |
TTL (seconds) of the in-process cache for semi-static data |
UPSTREAM_REQUESTS_PER_SECOND |
20 |
Pacing of upstream requests, must stay below OverFast's per-IP rate limit |
MAX_QUERY_DEPTH |
10 |
Maximum allowed GraphQL query depth |
MAX_QUERY_ALIASES |
15 |
Maximum aliases per query |
MAX_QUERY_TOKENS |
1000 |
Maximum tokens per query document |
LOG_LEVEL |
INFO |
Minimum level of application logs |
Queries are validated before execution: depth, alias count and document size are limited (see settings above). GraphiQL and introspection are enabled on purpose β this is a public API, they are the documentation.
All data comes from OverFast API, which itself scrapes Blizzard's official Overwatch pages. Overwatch is a trademark of Blizzard Entertainment, Inc. This project is not affiliated with Blizzard Entertainment.
Copyright Β© 2026 Valentin PORCHET.
This project is MIT licensed.