The Authorization Code Grant with PKCE is the only user-facing grant Flexslot supports. This page is the complete reference: every parameter, every header, every status code.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.
The flow at a glance
Why PKCE?
Without PKCE, anyone who intercepts the authorization code (browser history, server logs, a malicious app handling your custom URL scheme) can exchange it for tokens. PKCE binds the code to a secret your client generated before the flow started.- Your client generates
code_verifier— a random 43-128 character string. - Your client sends
code_challenge = BASE64URL(SHA256(code_verifier))to/authorize. - Flexslot stores the challenge against the issued code.
- When your client redeems the code at
/token, it sends the originalcode_verifier. - Flexslot recomputes
SHA256(code_verifier)and compares to the stored challenge.
invalid_grant and the code is invalidated. See RFC 7636 for the spec.
Generating PKCE values
Step 1 — Authorization request
Send the user’s browser to:Required query parameters
| Parameter | Value | Description |
|---|---|---|
response_type | code | The only supported value. |
client_id | cli_… | The client ID from the partner admin. |
redirect_uri | Full URL | Must exactly match a URI registered for this client. |
scope | Space-separated | E.g. decks:read sideboards:write. See Scopes. |
state | Random string | CSRF token. You’ll validate it on the callback. |
code_challenge | Base64url SHA-256 | From the PKCE generator above. |
code_challenge_method | S256 | Required. plain is forbidden. |
Example
All query parameter values must be percent-encoded. Use your language’s URL builder — don’t hand-roll the string.
Step 2 — User consents
Flexslot renders the consent screen, showing the user:- Your application’s name and logo (from the partner admin)
- The scopes you requested, in human-readable form
- A clear Allow and Deny option
code. If they click Deny, you get redirected with error=access_denied.
Step 3 — Callback to your redirect_uri
What you must verify
state matches
Compare to the value you stored when starting the flow. If it doesn’t match, abort — this is a CSRF attempt.
iss equals https://api.flexslot.gg
RFC 9207 mix-up defense. If a different
iss shows up, you’ve been redirected to the wrong AS.No error parameter
If
error=... is present, the flow failed. See errors.Step 4 — Token exchange
Body parameters
| Parameter | Required? | Description |
|---|---|---|
grant_type | Yes | authorization_code |
code | Yes | The code from the callback |
redirect_uri | Yes | Same URI you used in step 1 |
code_verifier | Yes | The PKCE verifier you generated in step 1 |
client_id | If public client | The client ID (confidential clients send it via Basic auth) |
Authentication
| Client type | How to authenticate |
|---|---|
| Confidential (has client_secret) | Authorization: Basic <base64(client_id:client_secret)> |
| Public (no secret) | Include client_id in the body |
Example request
Success response
200 OK, Content-Type: application/json, Cache-Control: no-store:
| Field | Description |
|---|---|
access_token | Send as Authorization: Bearer <token> on API requests. |
token_type | Always Bearer (or DPoP if you opted in — see DPoP). |
expires_in | Seconds until the access token expires. Currently 3600. |
refresh_token | Use to mint new access tokens. Rotates on every use. |
scope | The scopes actually granted. May be narrower than what you requested. |
Error response
400 Bad Request (or 401 for invalid_client), Content-Type: application/json:
Step 5 — Call the API
401 Unauthorized with a WWW-Authenticate header if the token is invalid, expired, or doesn’t have the right scope:
Step 6 — Refresh
When the access token expires (or proactively, ~5 minutes before it does), exchange the refresh token for a new pair:Common gotchas
| Symptom | Cause | Fix |
|---|---|---|
invalid_grant on first exchange | Code already used, or expired (60s) | Restart the flow |
invalid_grant with “code_verifier mismatch” | Verifier doesn’t match the challenge | Make sure you stored the verifier from the same session |
invalid_request with “redirect_uri mismatch” | Different URI in step 1 vs step 4 | They must be byte-for-byte identical |
invalid_client (401) | Wrong client_id, wrong secret, or wrong auth method | Check the partner admin |
unsupported_grant_type | You sent something other than authorization_code or refresh_token | Those are the only two grants supported |
Browser stuck on /authorize | redirect_uri isn’t registered for this client | Add it in the partner admin |
What to read next
Code samples
Full Express, Flask, and curl implementations
Security
PKCE, state, redirect URIs in depth
Errors
Every error code explained
DPoP
Sender-constrained tokens