OAuth
SlingLab is an OAuth 2.1 authorization server. A player signs in, approves the scopes you asked for, and your app receives an access token for that player. Create a confidential or public client in the console. Public clients, including MCP apps, can also register themselves.
Discovery:
GET https://slinglab.gg/.well-known/oauth-authorization-server
| Authorize | https://slinglab.gg/slinglab/api/oauth/authorize |
| Token | https://slinglab.gg/slinglab/api/oauth/token |
| Revoke | https://slinglab.gg/slinglab/api/oauth/revoke |
| Register | https://slinglab.gg/slinglab/api/oauth/register |
What a client may do
- Authorization code grant, with PKCE S256. Send
code_challengeandcode_challenge_method=S256. Aplainchallenge is rejected. - Refresh tokens. Each refresh replaces the previous refresh token. Sending an old refresh token revokes that token family.
stateon the authorize request. SlingLab returns it with the code.- A redirect URL that matches one you registered, character for character.
httpsis allowed.httpis allowed only for localhost. A native app can use a reverse-DNS scheme such ascom.example.app:/callback. Fragments, wildcards, and embedded usernames are rejected. - An optional
resource(RFC 8707). If you send it on authorize, send the same value on the token request. Usehttps://slinglab.gg/slinglab/apifor Your decks and Analytics, orhttps://slinglab.gg/mcpfor MCP.
Access tokens last 1 hour. Refresh tokens last 30 days. SlingLab does not offer the implicit grant, the password grant, or client credentials.
A confidential client sends its secret with HTTP Basic (client_id and client_secret) or as client_secret_post. A public client sends client_id and no secret. The token and revoke endpoints accept application/x-www-form-urlencoded only.
The token endpoint allows 30 requests a minute for each client. Revoke allows 30 a minute for each client. Registration allows 20 attempts an hour from one network address. Over the limit, the response is HTTP 429 with error rate_limited and a Retry-After header.
Dynamic registration (POST JSON to the register endpoint) creates a public client and returns client_id. It does not return a secret.
Scopes
Ask for the scopes you need, separated by spaces.
| Scope | What the player is sharing |
|---|---|
decks:read |
Every deck on their account, including private decks and decklists. See Your decks. |
analytics:read |
The same career and deck analytics as their Analytics page. See Analytics. |
games:read |
Finished games. Used by the MCP game history tool. |
cards:read |
Card lookup in the SlingLab catalog. |
rules:read |
The Magic Comprehensive Rules. |
mcp |
Opens the MCP endpoint. Each tool still needs its own scope. |
The player sees these on the consent screen. If they have already approved a set of scopes that covers the new request, SlingLab skips the screen. They can disconnect an app from the console.
Example
A confidential client asks for a player's decks. The response bodies are in Your decks and Analytics.
Send the player to authorize
Open this URL in the player's browser. code_challenge is the base64url SHA-256 of a code_verifier you keep on your server. This pair is the sample from RFC 7636: verifier dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk.
GET /slinglab/api/oauth/authorize?response_type=code&client_id=sl_app_your_client&redirect_uri=https%3A%2F%2Fexample.com%2Fcallback&scope=decks%3Aread&state=xyz&code_challenge=E9Melhoa2OwvFrEMTJguCHaoeK1t8URWbuGJSstw-cM&code_challenge_method=S256&resource=https%3A%2F%2Fslinglab.gg%2Fslinglab%2Fapi HTTP/1.1
Host: slinglab.gg
The player signs in and approves the scopes. SlingLab redirects to the URL you registered, with code and the same state:
https://example.com/callback?code=sl_ac_the_code&state=xyz
Exchange the code
POST /slinglab/api/oauth/token HTTP/1.1
Host: slinglab.gg
Content-Type: application/x-www-form-urlencoded
Authorization: Basic base64(client_id:client_secret)
grant_type=authorization_code&code=sl_ac_the_code&redirect_uri=https%3A%2F%2Fexample.com%2Fcallback&code_verifier=dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk&resource=https%3A%2F%2Fslinglab.gg%2Fslinglab%2Fapi
A public client sends client_id in the body and no secret. The token response:
{
"access_token": "sl_at_the_token",
"token_type": "Bearer",
"expires_in": 3600,
"refresh_token": "sl_rt_the_refresh",
"scope": "decks:read"
}
Call the API
GET /slinglab/api/v1/me/decks HTTP/1.1
Host: slinglab.gg
Authorization: Bearer sl_at_the_token
That body is Your decks. Add analytics:read to the authorize scope and the same pattern calls Analytics. MCP uses the token the same way, with resource https://slinglab.gg/mcp and the mcp scope.
Refresh
POST /slinglab/api/oauth/token HTTP/1.1
Host: slinglab.gg
Content-Type: application/x-www-form-urlencoded
Authorization: Basic base64(client_id:client_secret)
grant_type=refresh_token&refresh_token=sl_rt_the_refresh
The response is a new access token and a new refresh token. Store the new refresh token. The previous refresh token stops working, and sending it again revokes that token family.
Errors
Token and register errors use OAuth fields:
{ "error": "invalid_grant", "error_description": "Authorization code is invalid or expired." }
Revoke returns 200 and {} when the client authenticated, including when the token is already unknown.