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.