Skip to main content

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.

StatusReasonMeaning
200Stats returned
401missing_key / invalid_or_revoked_keyBad, missing, or revoked Authorization header
403guild_key_mismatchThis key is bound to a different guild than the one in the request
403no_global_consentTarget hasn't logged into ValCord, or has global consent off
403no_server_consentTarget hasn't allowed lookups in this specific server
404no_linked_accountTarget has consented but has no linked Valorant account
429Rate 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.

response (shape)
{
"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.

response (shape)
{
"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).

response (shape)
{
"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.

response (shape)
{ "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.