Skip to main content

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 gives you a connection. UX guidance on what to do with that connection — how to surface it, how to handle revocation, when to silently refresh vs prompt — is what separates a polished integration from a fragile one.

What good looks like

A well-built OAuth integration:

Names the connection clearly

“Connected to Flexslot as @username”, not “Token saved”.

Shows status, not just a button

Green check when healthy, yellow warning when reconnect needed, red when broken.

Handles 401 invisibly when possible

Silent refresh before the user notices. Prompt re-consent only when the refresh fails.

Provides a clear disconnect

One click. Calls /revoke. Confirms with the user. No leftover state.

The connection card

Most apps put the Flexslot connection on a Settings → Integrations page. Here’s a baseline pattern:
┌──────────────────────────────────────────────────────────┐
│  [Flexslot logo]   Flexslot                              │
│                                                          │
│  ✓ Connected as @george                                  │
│    Permissions: Read decks, Read sideboards              │
│    Last sync: 2 minutes ago                              │
│                                                          │
│            [Manage on Flexslot]    [Disconnect]          │
└──────────────────────────────────────────────────────────┘
When disconnected:
┌──────────────────────────────────────────────────────────┐
│  [Flexslot logo]   Flexslot                              │
│                                                          │
│  Connect your Flexslot account to sync decks and         │
│  sideboard guides.                                       │
│                                                          │
│                              [Connect Flexslot]          │
└──────────────────────────────────────────────────────────┘
When the grant was revoked or the refresh expired:
┌──────────────────────────────────────────────────────────┐
│  [Flexslot logo]   Flexslot                              │
│                                                          │
│  ⚠ Connection expired. Please reconnect.                 │
│    Your last sync was 47 days ago.                       │
│                                                          │
│                              [Reconnect Flexslot]        │
└──────────────────────────────────────────────────────────┘

Handling 401 from the API

A 401 on a normal API call means the access token failed. Three cases, three different responses:
1

Token expired (most common)

Refresh silently. The user sees nothing. If you proactively refresh 5 minutes before expiry, this should be rare.
2

Refresh succeeds, retry succeeds

Replace stored tokens, retry the original request. The user still sees nothing.
3

Refresh fails with invalid_grant

The grant was revoked — either by the user from /account/connected-apps, by Flexslot for suspected token abuse, or by your own bug (refresh token replay). Mark the user as disconnected and prompt them to reconnect.

Don’t loop

If a refresh succeeds but the retried call still 401s with invalid_token, don’t refresh again. That’s a bug pointing at your code, not at the user. Surface the error to your monitoring and show a polite error to the user — don’t infinite-loop the AS.

Token-bus pattern for parallel requests

A common source of invalid_grant storms: 10 in-flight requests all 401 at once, each independently tries to refresh, only one wins, the other 9 use the old refresh token, grant gets revoked. Pattern: a single token coordinator per user.
class FlexslotTokenStore {
  constructor() {
    this.refreshInFlight = new Map()  // userId -> Promise<tokens>
  }

  async getValidToken(userId) {
    const stored = await this.load(userId)
    if (Date.now() < stored.expiresAt - 5 * 60 * 1000) {
      return stored.accessToken
    }
    return this.refresh(userId, stored)
  }

  async refresh(userId, stored) {
    if (this.refreshInFlight.has(userId)) {
      return this.refreshInFlight.get(userId)
    }
    const p = this.doRefresh(stored).finally(() => this.refreshInFlight.delete(userId))
    this.refreshInFlight.set(userId, p)
    return p
  }
}
Across processes (a horizontally scaled service), use Redis with SET NX or a database row lock.

The user revoked you

A user can revoke your access at any time from flexslot.gg/account/connected-apps. You won’t know until the next API call returns 401 and your refresh returns invalid_grant. Don’t treat this as an error.
  • Mark the user as disconnected (clear the tokens).
  • Don’t email them about it (they revoked you on purpose).
  • Don’t try to reconnect silently.
  • The next time they use a Flexslot-dependent feature, show “Reconnect Flexslot”.
If your app sends scheduled or background sync jobs to Flexslot, stop them immediately when the grant is revoked. Repeated 401s on a revoked grant can trip Flexslot’s rate-limit and abuse heuristics.

When to start the flow

TriggerGood idea?
User clicks “Connect Flexslot”Yes
First time the user lands on a Flexslot-dependent pageYes, with explanation
In response to a refresh failureYes — show a banner, don’t auto-redirect
As soon as the user signs up for your productNo. They haven’t earned the trust.
On every loginNo. Tokens persist; only re-auth when broken.

Naming the connection

In your UI, refer to it consistently:
  • “Flexslot” — the platform
  • “Connected to Flexslot” — past tense, the state
  • “Connect Flexslot” — the call to action
  • “Flexslot account” — the user’s identity on flexslot.gg
Avoid “your Flexslot token” or “Flexslot API key” in user-facing copy. Those leak implementation details.

Showing what you can see

Users trust apps that are upfront about what they’re doing. On the connection card, list the scopes in plain language:
✓ Connected as @george
  This app can:
  • Read your decks
  • Read your sideboards
You can fetch the current granted scopes from your stored token (you got them back on the /token response) or by calling /introspect.

The Disconnect flow

1

User clicks Disconnect

Confirm with a modal: “Disconnect from Flexslot? Your synced data will stay; future syncs will stop until you reconnect.”
2

On confirm, call /revoke

POST the refresh token to https://api.flexslot.gg/api/public/v1/oauth/revoke. RFC 7009 always returns 200 — don’t worry about the response.
3

Clear your tokens

Delete access_token, refresh_token, expires_at from your store. Don’t delete the user’s synced Flexslot data unless they ask — they may reconnect later.
4

Update the UI

Switch the card to “Not connected” state.

Edge cases

User changes their Flexslot username

The sub claim in the introspection response is stable; the username may change. If you display the username on the connection card, refresh it periodically by calling /introspect.

User’s refresh token approaches expiry

Refresh tokens live 30 days from issue or last use. If the user opens your app once a week, their token will essentially never expire because every use extends it. If your app is used infrequently, expect cold-start 401s — handle them gracefully with a re-auth prompt.

Multiple browser tabs

If your app is a web SPA and the user has it open in two tabs, both tabs may try to refresh. See the token-bus pattern above, or use a BroadcastChannel to coordinate.

Background sync vs interactive use

Background workers using a user’s tokens are subject to the same lifetimes as foreground requests. If a background sync fails with invalid_grant, don’t retry — wait for the user to reconnect interactively. Background retries on a revoked grant can be flagged as abuse.

Patterns to avoid

Anti-patternWhy it’s bad
Showing the access_token to the userIt’s an internal credential; users don’t need to see it
Storing tokens in localStorageReadable by any XSS; use httpOnly cookies or in-memory
Polling /introspect on every API callWasteful and slow. Trust the access_token until it 401s.
Auto-reconnecting after revocationThe user revoked you on purpose. Respect that.
One huge “Sync everything” button that opens the OAuth flowSurprising. Users want to know what they’re agreeing to first.
Hiding the disconnect buttonOAuth requires an obvious way to disconnect.
Good OAuth UX is invisible when everything’s working and helpful when something breaks. If a user can’t tell whether they’re connected, you have a UX bug. If they can’t tell why they’re disconnected, you have another.