ValCord — API Reference
Base URL: https://api.valcord.smasse.xyz
This page covers the surface a bot integration actually uses: getting an Instance API Key and calling the stats/match-history endpoints. (The dashboard has its own session-cookie-based endpoints for login, linking accounts, and managing keys — those belong to ValCord's own first-party web app, not to a bot integration, so they aren't documented here. If you're extending or inspecting the full schema for some other reason, the machine-generated docs have everything: Swagger UI / raw OpenAPI schema.)
If you just want the one endpoint you need, skip ahead to Building a Bot for a walked-through example.
Authentication
A bot authenticates with an Instance API Key:
Authorization: Bearer <key> header on every /v1/stats/* endpoint below.
Generate one from the Hoster Portal
— log in with Discord, pick a server you have Manage Server permission on.
It's shown exactly once at creation, then only ever stored as a SHA-256
hash server-side.
POST /v1/stats/player
The core lookup — a summary stat line (rank, RR, K/D, win rate, etc.) for one consenting player.
Authorization: Bearer <instance api key>
Content-Type: application/json
{ "guild_id": 555000111222, "target_discord_id": 398175732393836544 }
Note these are numbers here, not strings — this request comes from a
bot's own backend process (not a browser), so there's no JS-safe-integer
concern; guild_id/target_discord_id are plain Discord snowflakes as
64-bit integers.
Rate limited to 30 requests/minute per Instance Key (not per IP — many
self-hosted bots can share an egress IP, so the key itself is the
principal being limited). Exceeding it returns 429.
| Status | Reason | Meaning |
|---|---|---|
| 200 | — | Stats returned |
| 401 | missing_key / invalid_or_revoked_key | Bad, missing, or revoked Authorization header |
| 403 | guild_key_mismatch | This key is bound to a different guild than the one in the request |
| 403 | no_global_consent | Target hasn't logged into ValCord, or has global consent off |
| 403 | no_server_consent | Target hasn't allowed lookups in this specific server |
| 404 | no_linked_account | Target has consented but has no linked Valorant account |
| 429 | — | Rate limit exceeded for this key |
Full error-handling walkthrough in Building a Bot.
POST /v1/stats/matches
Recent match history for a consenting player — same consent gate as
/v1/stats/player, same key, same guild binding. The difference is what
comes back: each match includes every participant, not just the target.
That's not a separate privacy decision — it's inherent to what a Valorant
match is. Riot's own match data always contains all 10 players, the same
way tracker.gg's match pages show everyone who was in the game. You still
can't use this (or any endpoint) to target a non-consenting Discord user
directly — the other 9 names per match are a side effect of a consenting
player's own history, never something you can look up in their own right.
Authorization: Bearer <instance api key>
Content-Type: application/json
{ "guild_id": 555000111222, "target_discord_id": 398175732393836544, "count": 5 }
count is optional (default 5, max 10) — number of recent matches to return.
{
"matches": [
{
"match_id": "...",
"map": "Ascent",
"mode": "Competitive",
"started_at": "2026-07-20T18:04:00Z",
"result": "win",
"rounds_won": 13,
"rounds_lost": 9,
"participants": [
{ "riot_game_name": "...", "riot_tag_line": "...", "team": "Blue",
"agent": "Jett", "kills": 22, "deaths": 14, "assists": 4,
"score": 312, "is_target": true },
{ "riot_game_name": "...", "riot_tag_line": "...", "team": "Blue",
"agent": "Sova", "kills": 15, "deaths": 16, "assists": 9,
"score": 240, "is_target": false }
]
}
]
}
Same status codes as /v1/stats/player (401/403/404/429), plus 422 if
count is outside 1–10. Shares that endpoint's 30 requests/minute budget —
per Instance Key, not per IP.
Endpoints with no player involved at all
Game content, the competitive leaderboard, and platform status aren't about
any specific Discord user, so none of them go through the consent gate
above — there's no target_discord_id in the request at all. They still
require a valid Instance API Key (just not one bound to a particular
guild), so an entirely unauthenticated caller can't use ValCord as a free
anonymous proxy for the Riot API.
GET /v1/content
Maps, agents, game modes, and acts — the reference data you need to turn the IDs in a match-history response into names a human recognizes.
{
"version": "...",
"maps": [{ "id": "...", "name": "Ascent", "asset_path": "..." }],
"agents": [{ "id": "...", "name": "Jett" }],
"game_modes": [{ "id": "...", "name": "Competitive" }],
"acts": [{ "id": "...", "name": "Episode 9 Act 2", "is_active": true }]
}
GET /v1/leaderboard
Riot's own public competitive leaderboard — the same names/ranks anyone
can see in-game. Query params: region (default na), act_id (optional,
defaults to the current active act).
{
"region": "na",
"act_id": "...",
"entries": [
{ "riot_game_name": "...", "riot_tag_line": "...", "leaderboard_rank": 1,
"ranked_rating": 999, "wins": 250 }
]
}
GET /v1/status
Maintenance/incident status for a region (default na) — the same data
Riot's own status page is built from.
{ "region": "na", "maintenances": [], "incidents": [] }
All three: 401 for a missing/invalid/revoked key, 429 on the shared
per-key rate limit. No 403/404 — there's no consent or linked-account
check to fail, since no player is being looked up.
GET /healthz
No auth. {"status": "ok"}. Used by the Docker healthcheck / uptime
monitoring, not part of the integration surface.