Skip to main content
Ten minutes from now you’ll have an access token in your terminal and you’ll have called the Flexslot API with it. We’ll use curl plus a tiny shell snippet so you can see exactly what’s happening on the wire.

Prerequisites

  • A Flexslot account (sign up if you don’t have one)
  • openssl, curl, and a Unix shell
  • A redirect URI you control. For this guide we’ll use http://localhost:8765/callback and run a one-line Python listener.

Step 1 — Register your client

1

Open the partner admin

Sign in at flexslot.gg and open Account → API access (the canonical link is flexslot.gg/account?section=api-access; the /settings URL redirects to /account). In the OAuth applications card, click New application.
2

Fill in the application

  • Name: Quickstart Test (whatever you like)
  • Application type: Public (no client secret) for a CLI/native app, or Confidential for a server app
  • Redirect URIs: http://localhost:8765/callback
  • Allowed scopes: check decks:read for now
3

Save the client_id

You’ll see a client_id like flx_client_01HX2K…. If you chose Confidential, you also get a client_secret shown once — copy it now.
Confidential client secrets are shown exactly once at creation time. If you lose it, rotate the secret from the admin — there’s no way to retrieve the original value.

Step 2 — Generate PKCE values

PKCE binds the authorization code to your client. Every authorization request needs a freshly generated code_verifier and code_challenge.
# Generate a random 32-byte code_verifier (43 chars base64url, no padding)
VERIFIER=$(openssl rand 32 | base64 | tr '+/' '-_' | tr -d '=\n')

# Derive the SHA-256 challenge
CHALLENGE=$(printf '%s' "$VERIFIER" | openssl dgst -sha256 -binary \
  | base64 | tr '+/' '-_' | tr -d '=\n')

echo "verifier=$VERIFIER"
echo "challenge=$CHALLENGE"
import crypto from 'node:crypto'

const b64url = (buf) =>
  buf.toString('base64').replace(/\+/g, '-').replace(/\//g, '_').replace(/=/g, '')

const verifier = b64url(crypto.randomBytes(32))
const challenge = b64url(crypto.createHash('sha256').update(verifier).digest())

console.log({ verifier, challenge })
import base64, hashlib, secrets

verifier = base64.urlsafe_b64encode(secrets.token_bytes(32)).rstrip(b'=').decode()
challenge = base64.urlsafe_b64encode(
    hashlib.sha256(verifier.encode()).digest()
).rstrip(b'=').decode()

print(f"verifier={verifier}")
print(f"challenge={challenge}")
Keep the verifier somewhere your callback handler can read it. We’ll need it in step 4. The challenge goes on the wire; the verifier stays secret in your app.

Step 3 — Send the user to /authorize

Open this URL in your browser. Replace CLIENT_ID and use the CHALLENGE you just generated.
STATE=$(openssl rand -hex 16)

open "https://flexslot.gg/oauth/authorize?\
response_type=code&\
client_id=CLIENT_ID&\
redirect_uri=http://localhost:8765/callback&\
scope=decks:read&\
state=$STATE&\
code_challenge=$CHALLENGE&\
code_challenge_method=S256"
You’ll see the Flexslot consent screen. Click Allow and your browser will be redirected to http://localhost:8765/callback?code=...&state=...&iss=https://api.flexslot.gg.

Step 4 — Capture the code with a one-line listener

Run this in a second terminal before you click Allow:
python3 -c "
from http.server import BaseHTTPRequestHandler, HTTPServer
from urllib.parse import urlparse, parse_qs
class H(BaseHTTPRequestHandler):
    def do_GET(s):
        q = parse_qs(urlparse(s.path).query)
        print('CODE=', q.get('code', [''])[0])
        print('STATE=', q.get('state', [''])[0])
        print('ISS=', q.get('iss', [''])[0])
        s.send_response(200); s.end_headers()
        s.wfile.write(b'You can close this tab.')
HTTPServer(('localhost', 8765), H).serve_forever()
"
After approving, your terminal prints the code. Validate that state matches the value you sent, and that iss equals https://api.flexslot.gg. If either check fails, stop — that’s an attack signal, not an error.

Step 5 — Exchange the code for tokens

CODE=<paste-the-code-from-step-4>

# For a Confidential client (with secret):
curl -sS -X POST https://api.flexslot.gg/api/public/v1/oauth/token \
  -u "$CLIENT_ID:$CLIENT_SECRET" \
  -d "grant_type=authorization_code" \
  -d "code=$CODE" \
  -d "redirect_uri=http://localhost:8765/callback" \
  -d "code_verifier=$VERIFIER"

# For a Public client (no secret):
curl -sS -X POST https://api.flexslot.gg/api/public/v1/oauth/token \
  -d "grant_type=authorization_code" \
  -d "client_id=$CLIENT_ID" \
  -d "code=$CODE" \
  -d "redirect_uri=http://localhost:8765/callback" \
  -d "code_verifier=$VERIFIER"
You’ll get back something like:
{
  "access_token": "flx_at_01HX2K6F8N9PRSTUVWXYZA…",
  "token_type": "Bearer",
  "expires_in": 3600,
  "refresh_token": "flx_rt_01HX2K6F8N9PRSTUVWXYZB…",
  "scope": "decks:read"
}

Step 6 — Call the API

ACCESS_TOKEN=<paste-the-access_token>

curl -sS https://api.flexslot.gg/api/public/v1/games/magic-the-gathering/decks \
  -H "Authorization: Bearer $ACCESS_TOKEN"
You should see a paginated list of the user’s decks. Congratulations, you have a working OAuth integration.

Step 7 — Identify the connected user

To render “Connected as: …” in your UI, call the user probe with the same access token. It returns the bare Firebase id and the user’s Flexslot handle:
curl -sS https://api.flexslot.gg/api/public/v1/_probe/user \
  -H "Authorization: Bearer $ACCESS_TOKEN"
{
  "caller": "partner:your-slug+user:gZUcFcNiXQNFH3unke6X8gVox5Y2",
  "user": {
    "id": "gZUcFcNiXQNFH3unke6X8gVox5Y2",
    "username": "george-cardeio"
  },
  "scopes": ["decks:read"],
  "tier": "standard"
}
user.id is the bare Firebase id and is identical to author.id on that user’s public decks — use it to correlate the connected account with the deck data you fetch. user.username is the same handle shown on the consent screen. No extra scope (no profile/email) is required: identity here is exactly what the user already saw and approved at consent. The user’s email is intentionally not returned — it isn’t consistently shown at consent, so it would be undisclosed PII for a partner that only requested resource scopes. caller is an opaque composite principal — stable for rate-limiting and audit, but not meant for display.

What you just did

1

Registered a client

The partner admin gave you a client_id (and optionally a client_secret) that identifies your app.
2

Generated PKCE

The code_verifier proves your app is the same app that initiated the flow, even if someone intercepts the code.
3

Got user consent

The user saw exactly which scopes you requested and clicked Allow.
4

Validated the response

You checked state (CSRF defense) and iss (mix-up defense, RFC 9207).
5

Exchanged code for tokens

The token endpoint verified the code_verifier and returned access + refresh tokens.
6

Called the API

A Bearer token in the Authorization header is all the API needs.

Next steps

Code samples

Production-ready Express and Flask implementations

Authorization Code Flow

The full flow with a sequence diagram

Refresh tokens

How to keep the user logged in

Scopes

Request additional permissions
This quickstart used a public client (no secret) to keep things simple. For a server-side app, choose Confidential in step 1 and send the client_secret via HTTP Basic auth as shown in step 5.