Skip to main content
Scopes are how your app tells Flexslot — and the user — exactly what it wants to do. Each scope is a short, colon-separated string. You request them as a space-separated list on the /authorize request:
scope=decks:read sideboards:write guides:read
The user sees a humanized version on the consent screen.

Catalog

Scopes are additive and minimum-necessary. Request only what you need for the operation in front of the user. You can request more scopes later via incremental authorization (start a new flow with the additional scopes).

Decks

ScopeGrants
decks:readList the user’s decks, read deck details, read cards, read deck metadata
decks:writeCreate, update, and delete the user’s decks. Modify cards. Does not include read access — also request decks:read to list or fetch decks.

Sideboard guides

Gates the standalone sideboard-guide resource (/games/{game}/sideboard-guides) and the per-opponent matchup guides nested inside each one.
ScopeGrants
sideboards:readList sideboard guides and read their contents, including the nested matchup guides (opponent archetype, sideboarding plan, play/draw notes)
sideboards:writeCreate, update, and delete sideboard guides and their matchup guides; attach/detach decks. Does not include read access — also request sideboards:read to list or fetch them.

Deck guides

Gates a deck’s own in-deck strategy guide (/games/{game}/decks/{id}/guide) — the Markdown/TipTap write-up attached to a single deck. This is not the sideboard-guide resource above; a “sideboard guide” is gated by sideboards:*, not guides:*.
ScopeGrants
guides:readRead a deck’s in-deck strategy guide (premium guides are preview-truncated for non-owners without content access)
guides:writeCreate, replace, and delete a deck’s in-deck strategy guide. Does not include read access — also request guides:read to read it.

Cards

ScopeGrants
cards:readLook up cards in the Flexslot card database (autocomplete, batch fetch, card details)
cards:read does not access any user-specific data. It’s the same read-only card data available on the public site. Most integrations need this alongside decks:read because deck contents reference cards.

Exports

ScopeGrants
exports:readGenerate Excel, DOCX, and PDF exports of the user’s decks and sideboard guides

How scope strings combine

Request multiple scopes by separating them with spaces (which are %20 once URL-encoded):
scope=decks:read sideboards:read guides:read cards:read
Scopes are a flat vocabulary with no hierarchy. decks:write does not include decks:read — if your app both reads and writes decks, request both explicitly (scope=decks:read decks:write). The resource server checks each endpoint’s required scope with an exact subset match, so a token holding only decks:write gets 403 FLEXSLOT_INSUFFICIENT_SCOPE on any read endpoint.

What the user sees

For scope=decks:read sideboards:write, the consent screen shows:
Quickstart Test wants to:
  • Read your decks
  • Manage your sideboards (create, edit, delete)
Phrasing comes from a Flexslot-controlled catalog. You can’t customize the text.

Allow or Deny — there’s no partial grant

At the consent screen the user makes a single binary decision:
  • Allow — Flexslot grants exactly the scopes you requested (a decks:write request never silently collapses to decks:read). The authorization code, and the access token minted from it, carry your full requested scope set.
  • Deny — you receive ?error=access_denied&error_description=...&state=...&iss=... at your redirect URI and no code is issued.
There are no per-scope toggles on the consent screen — the user cannot approve a subset. If any requested scope is not in the client’s registered allowed_scopes, the whole request fails up front with error=invalid_scope (a redirect error before consent is ever shown). The /token response still echoes the granted scope for forward-compatibility — read it rather than assuming — but on the authorization-code grant it equals what you requested:
{
  "access_token": "flx_at_…",
  "scope": "decks:read sideboards:write",
  "...": "..."
}

Narrowing scope yourself on refresh

You can deliberately request a narrower token on a grant_type=refresh_token exchange by passing a scope param. The requested scopes must be a subset of the original grant; anything outside it returns error=invalid_scope. Omit scope to keep the full grant. This is the only path that produces a token with fewer scopes than the grant, and it is opt-in by the client — Flexslot never narrows on its own.

Scope enforcement on the API

The resource server checks scopes per-endpoint. A token with only decks:read calling a decks:write endpoint gets the canonical Flexslot envelope — there is no WWW-Authenticate header on the Bearer lane:
HTTP/1.1 403 Forbidden
Content-Type: application/json
{
  "error": "FLEXSLOT_INSUFFICIENT_SCOPE",
  "message": "Caller does not have the required scope(s).",
  "details": {
    "required": ["decks:write"],
    "granted": ["decks:read"]
  }
}
When you see FLEXSLOT_INSUFFICIENT_SCOPE, read details.required for the missing scope. The fix is not to retry — it’s to start a new authorization flow that requests the missing scope.

Incremental authorization

Instead of requesting every possible scope on first login, start with the minimum and request more when you need them.
1

First login: minimum scopes

scope=decks:read — just enough to show the user their decks.
2

User clicks Export

Now you need exports:read. Start a new flow with scope=decks:read exports:read.
3

User keeps using the app

The new token has both scopes. You don’t lose the user’s prior consent — Flexslot remembers it.
This pattern has two big wins:
  • Trust: users are warier of apps that demand everything up front
  • Recovery: if a scope check fails on the API, you have a clear path forward
Don’t request scopes you might use someday. The minimum-necessary rule is enforced socially (users notice, write-ups happen) and reputationally. Pages with sprawling consent screens convert worse.

Scope vs role

Scope is what the app may attempt. The user’s permissions on the specific resource still decide whether the action succeeds. When the action isn’t permitted, the resource server returns a 403 with a non-FLEXSLOT_INSUFFICIENT_SCOPE error code — for example FLEXSLOT_NOT_DECK_OWNER when the caller tries to act on a deck the user doesn’t own.

User identity — no scope required

There is no profile or email scope. Any OAuth access token can call GET /_probe/user to retrieve the connected user’s display identity — user.id (bare Firebase id, matches author.id on their decks) and user.username (the handle shown on the consent screen). That’s everything you need for a “Connected as …” UI, and it comes free with the grant — request only the resource scopes you actually act on (decks:read, etc.). The user’s email is not returned: the consent screen shows the username (it falls back to email only when a user has no username), so email isn’t something every user reliably approves sharing — exposing it to a decks:read-only partner would be undisclosed PII. See Identify the connected user.

Future scopes

We add scopes when we add public API surface. If you need access to a Flexslot capability that doesn’t have a scope yet, open a feedback ticket — these docs and the scope catalog are kept in sync.