> ## Documentation Index
> Fetch the complete documentation index at: https://docs.flexslot.gg/llms.txt
> Use this file to discover all available pages before exploring further.

# OAuth Quickstart

> Get your first OAuth integration working in 10 minutes

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](https://flexslot.gg) 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

<Steps>
  <Step title="Open the partner admin">
    Sign in at [flexslot.gg](https://flexslot.gg) and open **Account → API access** (the canonical link is [flexslot.gg/account?section=api-access](https://flexslot.gg/account?section=api-access); the `/settings` URL redirects to `/account`). In the **OAuth applications** card, click **New application**.
  </Step>

  <Step title="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
  </Step>

  <Step title="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.
  </Step>
</Steps>

<Warning>
  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.
</Warning>

## 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`.

<CodeGroup>
  ```bash bash theme={null}
  # 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"
  ```

  ```javascript node theme={null}
  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 })
  ```

  ```python python theme={null}
  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}")
  ```
</CodeGroup>

<Note>
  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.
</Note>

## Step 3 — Send the user to /authorize

Open this URL in your browser. Replace `CLIENT_ID` and use the `CHALLENGE` you just generated.

```bash theme={null}
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:

```bash theme={null}
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

```bash theme={null}
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:

```json theme={null}
{
  "access_token": "flx_at_01HX2K6F8N9PRSTUVWXYZA…",
  "token_type": "Bearer",
  "expires_in": 3600,
  "refresh_token": "flx_rt_01HX2K6F8N9PRSTUVWXYZB…",
  "scope": "decks:read"
}
```

## Step 6 — Call the API

```bash theme={null}
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:

```bash theme={null}
curl -sS https://api.flexslot.gg/api/public/v1/_probe/user \
  -H "Authorization: Bearer $ACCESS_TOKEN"
```

```json theme={null}
{
  "caller": "partner:your-slug+user:gZUcFcNiXQNFH3unke6X8gVox5Y2",
  "user": {
    "id": "gZUcFcNiXQNFH3unke6X8gVox5Y2",
    "username": "george-cardeio"
  },
  "scopes": ["decks:read"],
  "tier": "standard"
}
```

<Note>
  `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.
</Note>

## What you just did

<Steps>
  <Step title="Registered a client">
    The partner admin gave you a `client_id` (and optionally a `client_secret`) that identifies your app.
  </Step>

  <Step title="Generated PKCE">
    The `code_verifier` proves your app is the same app that initiated the flow, even if someone intercepts the code.
  </Step>

  <Step title="Got user consent">
    The user saw exactly which scopes you requested and clicked Allow.
  </Step>

  <Step title="Validated the response">
    You checked `state` (CSRF defense) and `iss` (mix-up defense, [RFC 9207](https://www.rfc-editor.org/rfc/rfc9207)).
  </Step>

  <Step title="Exchanged code for tokens">
    The token endpoint verified the `code_verifier` and returned access + refresh tokens.
  </Step>

  <Step title="Called the API">
    A `Bearer` token in the `Authorization` header is all the API needs.
  </Step>
</Steps>

## Next steps

<CardGroup cols={2}>
  <Card title="Code samples" icon="code" href="/oauth/code-samples">
    Production-ready Express and Flask implementations
  </Card>

  <Card title="Authorization Code Flow" icon="diagram-project" href="/oauth/authorization-code-flow">
    The full flow with a sequence diagram
  </Card>

  <Card title="Refresh tokens" icon="rotate" href="/oauth/security#refresh-token-rotation">
    How to keep the user logged in
  </Card>

  <Card title="Scopes" icon="list-check" href="/oauth/scopes">
    Request additional permissions
  </Card>
</CardGroup>

<Tip>
  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.
</Tip>
