Two surfaces share the name “apps” — don’t conflate them. This page is about the “Connected to Flexslot” card you build inside your product, where your end users see and disconnect their Flexslot connection. Separately, you (the partner) manage your own OAuth clients —
client_id, secret, redirect URIs — from Account → API access on flexslot.gg. See Managing OAuth Clients for that.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:Handling 401 from the API
A401 on a normal API call means the access token failed. Three cases, three different responses:
Token expired (most common)
Refresh silently. The user sees nothing. If you proactively refresh 5 minutes before expiry, this should be rare.
Refresh succeeds, retry succeeds
Replace stored tokens, retry the original request. The user still sees nothing.
Don’t loop
If a refresh succeeds but the retried call still 401s (e.g.FLEXSLOT_OAUTH2_TOKEN_EXPIRED or FLEXSLOT_AUTHENTICATION_FAILED), 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 ofinvalid_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.
SET NX or a database row lock.
The user revoked you
A user can revoke your access at any time fromflexslot.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”.
When to start the flow
| Trigger | Good idea? |
|---|---|
| User clicks “Connect Flexslot” | Yes |
| First time the user lands on a Flexslot-dependent page | Yes, with explanation |
| In response to a refresh failure | Yes — show a banner, don’t auto-redirect |
| As soon as the user signs up for your product | No. They haven’t earned the trust. |
| On every login | No. 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
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:/token response) or by calling /introspect.
The Disconnect flow
User clicks Disconnect
Confirm with a modal: “Disconnect from Flexslot? Your synced data will stay; future syncs will stop until you reconnect.”
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.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.Edge cases
Identifying the user across sessions
Two endpoints, two jobs:GET /_probe/useris what you call to render the connection card. With the user’s access token it returnsuser.id(the bare, stable Firebase id — identical toauthor.idon that user’s public decks) anduser.username(the handle shown on the consent screen, e.g.george-cardeio). No extra scope is required; identity here is exactly what the user approved at consent. Treatusernameas a mutable display field anduser.idas the stable correlation key. The user’s email is not returned — it isn’t consistently shown at consent, so it would be undisclosed PII; if you need to contact the user, ask them in your own product. See the quickstart for the full response shape.POST /introspectis a token-metadata endpoint (RFC 7662), not an identity endpoint. Itsusernamefield carries the stable Firebase id (the same value as_probe/user’suser.id), not a display name. There is nosubclaim — this is a plain OAuth 2.0 authorization server, not OIDC. Use/introspectto check whether a token is live and what it’s scoped to; use_probe/userto show who’s connected.
caller field on _probe/user (partner:<slug>+user:<firebaseId>) is an opaque composite principal for rate-limiting and audit — stable, but don’t display it. Key your own profile store on user.id, which is the same firebase id /introspect returns as username.
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 aBroadcastChannel 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 withinvalid_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-pattern | Why it’s bad |
|---|---|
| Showing the access_token to the user | It’s an internal credential; users don’t need to see it |
| Storing tokens in localStorage | Readable by any XSS; use httpOnly cookies or in-memory |
| Polling /introspect on every API call | Wasteful and slow. Trust the access_token until it 401s. |
| Auto-reconnecting after revocation | The user revoked you on purpose. Respect that. |
| One huge “Sync everything” button that opens the OAuth flow | Surprising. Users want to know what they’re agreeing to first. |
| Hiding the disconnect button | OAuth requires an obvious way to disconnect. |