Analytics
GET https://slinglab.gg/slinglab/api/v1/me/analytics
Returns one account's career stats, per-deck performance, and the first page of recent games. The numbers match that player's Analytics page.
Authentication
An API key with analytics:read returns the key owner's account. Send it as X-API-Key or Authorization: Bearer sk_live_....
An OAuth access token with analytics:read returns the player who granted it:
GET /slinglab/api/v1/me/analytics?hide_short_games=false HTTP/1.1
Host: slinglab.gg
Authorization: Bearer sl_at_the_token
If you set a resource during the grant, use https://slinglab.gg/slinglab/api.
A key allows 60 requests a minute and 5,000 a day. An access token on this route allows 60 requests a minute for that player. Over the limit, the response is HTTP 429 with Retry-After. A rejected key uses the error body in API keys. A rejected token uses the error body in OAuth.
Query
| Parameter | Value |
|---|---|
hide_short_games |
false includes short games. Omit it, or send any other value, and short games stay out of the stats. |
format |
A format key of 1 to 40 letters, such as commander. Any other value returns 400. |
A rejected format is:
{ "error": "bad_request", "error_description": "format is not a recognized format key." }
Body
recent_games is the first 25 finished games. This route does not page further. Later pages are the game_history tool in MCP.
The body includes opponent display names and user ids, the same way the Analytics page does. It leaves out avatars. Mystery Commander matches stay in recent_games and stay out of the aggregate stats.
win_rate is a fraction from 0 to 1, or null when games is 0.
{
"filters": { "hide_short_games": true, "format": null },
"viewer_shares_decks": true,
"career": {
"games": 40,
"wins": 12,
"win_rate": 0.3,
"total_duration_ms": 72000000,
"avg_duration_ms": 1800000,
"avg_rounds": 8.2,
"replays": 30,
"current_streak": { "result": "win", "count": 2 },
"best_win_streak": 4,
"recent_form": [
{
"roomId": "room_id",
"result": "win",
"place": 1,
"pod": 4,
"format": "commander",
"endedAt": "2026-01-01T00:00:00.000Z"
}
],
"finishes": {
"games": 40,
"by_pod": [{ "pod": 4, "games": 40, "places": [12, 10, 10, 8] }],
"outlasted": 0.55,
"last_place": 0.2
},
"formats": [{ "format": "commander", "game_family": "mtg", "games": 40, "wins": 12 }],
"top_decks": [{ "deck_id": "deck_id", "name": "Deck name", "games": 18, "wins": 6 }],
"rivals": [{ "user_id": "user_id", "name": "Rival", "games": 5, "wins": 2 }],
"colors": {
"played": [{ "identity": "WUBG", "games": 18, "wins": 6 }],
"faced": [{ "identity": "UB", "games": 9, "wins": 3 }],
"baseline": { "games": 40, "wins": 12 },
"opponents_hidden": false
}
},
"decks": [
{
"deck_id": "deck_id",
"name": "Deck name",
"games": 18,
"wins": 6,
"win_rate": 0.333,
"avg_placement": 2.1,
"finishes": { "games": 18, "by_pod": [], "outlasted": 0.6, "last_place": 0.1 },
"avg_duration_ms": 1900000,
"avg_rounds": 8.4,
"last_played_at": "2026-01-01T00:00:00.000Z",
"replays": 14,
"recent_form": [{ "place": 1, "pod": 4 }],
"trend": [0, 0.5, 0.33],
"outlasted_trend": [1, 0.66, 0.6],
"versions": [
{ "version_id": "version_id", "version_number": 2, "games": 10, "wins": 4, "outlasted": 0.62 }
],
"opponents": [{ "user_id": "user_id", "name": "Rival", "games": 5, "wins": 2 }],
"commander": {
"games": 18,
"games_cast": 16,
"total_casts": 20,
"avg_first_cast_turn": 4.2,
"wins_when_cast": 6,
"wins_when_not_cast": 0
},
"color_matchups": {
"games": 18,
"wins": 6,
"identities": [{ "identity": "UB", "games": 4, "wins": 1 }]
}
}
],
"matchup_opponents": [{ "user_id": "user_id", "name": "Rival", "games": 5 }],
"matchups": [
{ "deck_id": "deck_id", "opponent_user_id": "user_id", "games": 5, "wins": 2, "ahead": 3, "behind": 2 }
],
"recent_games": [
{
"room_id": "room_id",
"room_code": "ABCD",
"has_replay": true,
"game_family": "mtg",
"format": "commander",
"player_count": 4,
"turn_rounds": 8,
"duration_ms": 3600000,
"started_at": "2026-01-01T00:00:00.000Z",
"ended_at": "2026-01-01T01:00:00.000Z",
"result": "win",
"declared_by_vote": false,
"you": {
"user_id": "user_id",
"name": "You",
"placement": 1,
"life_at_end": 40,
"conceded": false,
"deck": { "id": "deck_id", "name": "Deck name", "is_public": true },
"deck_hidden_reason": null
},
"opponents": []
}
]
}
colors is null when no counted game has a known color identity. commander and color_matchups are null when that deck has no data for them. deck_hidden_reason is null, opponent-opted-out, or you-opted-out when a recorded deck is hidden.
career.recent_form keeps the field names roomId and endedAt. decks[].recent_form is the last finishes for that deck, oldest first, each { "place", "pod" }. trend is the running win rate after each game, oldest first. outlasted_trend is the running mean share of opponents outlasted, sampled at the same points.
Successful responses include Cache-Control: no-store. A key response also includes X-RateLimit-Remaining and X-RateLimit-Daily-Remaining.