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

# Migrating from Personal Access Tokens

> Add OAuth alongside your existing PAT-based integration

If you're already using a Personal Access Token (PAT) to call the Flexslot API, this page shows you how to add OAuth so multiple users can connect their own accounts. You don't have to remove PAT support — the two coexist cleanly.

## When to migrate

PATs and OAuth solve different problems. Migrate when at least one of these is true:

<CardGroup cols={2}>
  <Card title="Multiple users" icon="users">
    Your app is used by more than one Flexslot account. PATs are one-per-user; OAuth lets each user connect their own account.
  </Card>

  <Card title="You don't want to handle credentials" icon="lock">
    With OAuth, users never share their Flexslot password or token with you. They consent on flexslot.gg.
  </Card>

  <Card title="You need scope-limited access" icon="list-check">
    PATs grant full account access. OAuth tokens are scoped to exactly what you requested.
  </Card>

  <Card title="You need revocation hygiene" icon="rotate">
    OAuth tokens auto-expire (1h access, 30d refresh) and can be revoked per-grant from `/account/connected-apps`.
  </Card>
</CardGroup>

If you're scripting against your own account and nothing else, **stay on PAT**. It's simpler and that's what it's for.

## Side-by-side

The API call is identical. The difference is **how you get the token**.

<CodeGroup>
  ```javascript PAT — before theme={null}
  const PAT = process.env.FLEXSLOT_PAT  // user pasted this into your config

  const res = await fetch('https://api.flexslot.gg/api/public/v1/games/magic-the-gathering/decks', {
    headers: { 'Authorization': `Bearer ${PAT}` },
  })
  ```

  ```javascript OAuth — after theme={null}
  // PAT replaced with a per-user access_token from your DB
  const accessToken = await getAccessTokenForUser(userId)  // your function

  const res = await fetch('https://api.flexslot.gg/api/public/v1/games/magic-the-gathering/decks', {
    headers: { 'Authorization': `Bearer ${accessToken}` },
  })
  ```
</CodeGroup>

The `Authorization` header is the same. Everything underneath — token lifetime, refresh, scope checks — changes.

## Migration plan

<Steps>
  <Step title="Register an OAuth client">
    Account → API access → OAuth applications → New application. See [Quickstart Step 1](/oauth/quickstart#step-1-register-your-client).
  </Step>

  <Step title="Add a 'Connect Flexslot' button">
    Sends the user through the OAuth flow ([authorization code with PKCE](/oauth/authorization-code-flow)).
  </Step>

  <Step title="Store tokens per-user">
    Add `flexslot_access_token`, `flexslot_refresh_token`, `flexslot_expires_at` to your user record. Encrypt at rest.
  </Step>

  <Step title="Pick the token at call time">
    If the user connected via OAuth, use their access token (refreshing if needed). Otherwise fall back to the PAT.
  </Step>

  <Step title="Migrate users gradually">
    Encourage existing users to reconnect via OAuth. You can keep PAT support indefinitely; this is purely a UX upgrade.
  </Step>
</Steps>

## Token selection pattern

A common pattern: a single function that returns "the best available credential" for a given user.

<CodeGroup>
  ```javascript Node.js theme={null}
  async function getFlexslotToken(userId) {
    const user = await db.users.findById(userId)

    // Preferred: OAuth
    if (user.flexslot_refresh_token) {
      if (Date.now() < user.flexslot_expires_at - 5 * 60 * 1000) {
        return user.flexslot_access_token
      }
      return await refreshFlexslotToken(user)
    }

    // Fallback: PAT
    if (user.flexslot_pat) {
      return user.flexslot_pat
    }

    throw new NotConnectedError(`User ${userId} hasn't connected Flexslot`)
  }
  ```

  ```python Python theme={null}
  def get_flexslot_token(user_id: int) -> str:
      user = db.users.find_by_id(user_id)

      if user.flexslot_refresh_token:
          if time.time() < user.flexslot_expires_at - 5 * 60:
              return user.flexslot_access_token
          return refresh_flexslot_token(user)

      if user.flexslot_pat:
          return user.flexslot_pat

      raise NotConnectedError(f"User {user_id} hasn't connected Flexslot")
  ```
</CodeGroup>

## What changes for your users

| Concern               | PAT                                       | OAuth                                                               |
| --------------------- | ----------------------------------------- | ------------------------------------------------------------------- |
| How they grant access | Paste a token from settings into your app | Click "Connect Flexslot", consent on flexslot.gg                    |
| Scope of access       | Full account                              | Exactly the scopes you requested                                    |
| Lifetime              | Until manually revoked                    | Access: 1h, Refresh: 30d, auto-rotated                              |
| Revocation            | Manual via Flexslot settings              | One click in `/account/connected-apps`                              |
| Visibility            | Token in their hands and yours            | Token only on your server; user sees a "Connected to YourApp" entry |
| Multi-user support    | One PAT per user, each pasted manually    | Per-user via consent flow                                           |
| User trust            | Token is a credential                     | Standard "Sign in with Flexslot" UX                                 |

## What stays the same

* **Endpoints**: identical. `GET /api/public/v1/games/magic-the-gathering/decks`, etc.
* **Response shapes**: identical.
* **Pagination, filtering**: identical.
* **Webhook signing**: not affected — that's HMAC, separate from auth.
* **Rate limits**: same per-account limits whether you use PAT or OAuth.

## Adding the "Connect" UI

Most apps add a section to their existing settings page:

> **Flexslot Integration**
> ☑ Connected as **@username**\
> \[Disconnect]
>
> or
>
> ☐ Not connected\
> \[Connect Flexslot]

The **Connect** button sends the user through the OAuth flow. The **Disconnect** button:

1. Calls `POST /api/public/v1/oauth/revoke` with the user's refresh token
2. Clears `flexslot_access_token`, `flexslot_refresh_token`, `flexslot_expires_at` from your DB

```bash theme={null}
curl -X POST https://api.flexslot.gg/api/public/v1/oauth/revoke \
  -u "$CLIENT_ID:$CLIENT_SECRET" \
  -d "token=$REFRESH_TOKEN" \
  -d "token_type_hint=refresh_token"
```

See [Connected Apps UX](/oauth/connected-apps-ux) for the full UX pattern, including what to do when a user revokes from the Flexslot side.

## Common pitfalls when migrating

| Pitfall                                            | Why                                          | Fix                                                                                                              |
| -------------------------------------------------- | -------------------------------------------- | ---------------------------------------------------------------------------------------------------------------- |
| Storing PAT and OAuth in the same column           | Type confusion at call time                  | Separate columns: `flexslot_pat`, `flexslot_access_token`, `flexslot_refresh_token`                              |
| Forgetting to handle `invalid_grant` on refresh    | User looks logged in but the next call fails | Catch on refresh, mark user as disconnected, prompt re-auth                                                      |
| Asking for too many scopes up front                | User declines, conversion drops              | Start with minimum scopes, request more via [incremental authorization](/oauth/scopes#incremental-authorization) |
| Skipping `state` because "you have PKCE"           | They defend different attacks                | Use both. Always.                                                                                                |
| Logging the access\_token in your migration runner | Tokens persist in log aggregator             | Mask before logging                                                                                              |

## Should you remove PAT support?

You don't have to. Some users prefer PATs:

* Scripts that don't have a browser
* CI integrations
* Personal automation

A clean approach: **keep PAT support for personal use, OAuth for user-facing apps**. The two patterns serve different audiences.

If you do want to deprecate, give users a deprecation timeline and an obvious upgrade path. Don't break their integrations silently.

## Next steps

<CardGroup cols={2}>
  <Card title="Quickstart" icon="rocket" href="/oauth/quickstart">
    Get the first end-to-end flow working
  </Card>

  <Card title="Connected Apps UX" icon="plug" href="/oauth/connected-apps-ux">
    The UI patterns users expect
  </Card>

  <Card title="Scopes" icon="list-check" href="/oauth/scopes">
    Pick the right minimum-viable set
  </Card>

  <Card title="Errors" icon="circle-exclamation" href="/oauth/errors">
    Handle revocation and refresh failures
  </Card>
</CardGroup>
