> ## Documentation Index
> Fetch the complete documentation index at: https://docs.monei.cc/llms.txt
> Use this file to discover all available pages before exploring further.

# Handling Partial Grants

> Users can approve fewer scopes than your app requests. Here's how to detect and handle that gracefully

Monei gives users granular control over scope approvals. When your app requests multiple scopes, the user can approve all of them, some of them, or none of them individually.

This means **your app will sometimes receive fewer scopes than it requested.** This is not an error, it's by design. Building around it is a requirement, not an edge case.

***

## How to detect what was granted

The `scopes` field in the token response tells you exactly what the user approved:

```json theme={null}
{
  "access_token":  "mct_...",
  "refresh_token": "mcr_...",
  "token_type":    "Bearer",
  "expires_in":    3600,
  "scopes":        ["wallet:read", "profile:read"]
}
```

If you requested `wallet:read wallet:send wallet:withdraw` but the user only approved `wallet:read` and `profile:read`, the response will only contain those two.

***

## Check and store granted scopes

Always inspect the `scopes` field immediately after token exchange and store it:

<CodeGroup>
  ```javascript Node.js theme={null}
  const tokens = await exchangeCodeForTokens(code);

  const granted = new Set(tokens.scopes);

  const hasWalletRead     = granted.has('wallet:read');
  const hasWalletSend     = granted.has('wallet:send');
  const hasWalletWithdraw = granted.has('wallet:withdraw');
  const hasBillsPay       = granted.has('bills:pay');
  const hasOfframp        = granted.has('offramp:execute');

  // Store what was actually granted
  await db.users.update(userId, {
    moneiAccessToken:  encrypt(tokens.access_token),
    moneiRefreshToken: encrypt(tokens.refresh_token),
    moneiTokenExpiry:  Date.now() + (tokens.expires_in * 1000),
    moneiScopes:       tokens.scopes, // ← what was actually granted
  });
  ```

  ```python Python theme={null}
  tokens = exchange_code_for_tokens(code)

  granted = set(tokens["scopes"])

  has_wallet_read     = "wallet:read"     in granted
  has_wallet_send     = "wallet:send"     in granted
  has_wallet_withdraw = "wallet:withdraw" in granted
  has_bills_pay       = "bills:pay"       in granted
  has_offramp         = "offramp:execute" in granted

  # Store what was actually granted
  db.users.update(user_id, {
      "monei_access_token":  encrypt(tokens["access_token"]),
      "monei_refresh_token": encrypt(tokens["refresh_token"]),
      "monei_token_expiry":  tokens["expires_in"],
      "monei_scopes":        tokens["scopes"],  # ← what was actually granted
  })
  ```
</CodeGroup>

***

## Show users what they can do

Don't silently break features. Show users clearly what is available based on what they granted:

<CodeGroup>
  ```javascript Node.js theme={null}
  function getAvailableFeatures(scopes) {
    const granted = new Set(scopes);

    return {
      canViewBalance:   granted.has('wallet:read'),
      canSendToFriend:  granted.has('wallet:send'),
      canWithdrawToBank: granted.has('wallet:withdraw'),
      canPayBills:      granted.has('bills:pay'),
      canOfframp:       granted.has('offramp:execute'),
    };
  }

  // In your UI layer
  const features = getAvailableFeatures(user.moneiScopes);

  if (!features.canWithdrawToBank) {
    showBanner(
      'Bank withdrawal is not enabled. ' +
      'Reconnect your Monei account to enable it.'
    );
  }
  ```

  ```python Python theme={null}
  def get_available_features(scopes: list[str]) -> dict:
      granted = set(scopes)
      return {
          "can_view_balance":    "wallet:read"     in granted,
          "can_send_to_friend":  "wallet:send"     in granted,
          "can_withdraw_to_bank": "wallet:withdraw" in granted,
          "can_pay_bills":       "bills:pay"       in granted,
          "can_offramp":         "offramp:execute" in granted,
      }

  # In your view layer
  features = get_available_features(user["monei_scopes"])

  if not features["can_withdraw_to_bank"]:
      show_banner(
          "Bank withdrawal is not enabled. "
          "Reconnect your Monei account to enable it."
      )
  ```
</CodeGroup>

***

## Re-request a specific scope

If a user tries to use a feature that requires a scope they didn't grant, you can send them through the authorization flow again requesting only the missing scope. Monei will show just the new scope on the consent screen. Scopes already granted are not shown again.

<CodeGroup>
  ```javascript Node.js theme={null}
  app.get('/enable-withdrawals', (req, res) => {
    const state = crypto.randomBytes(16).toString('hex');
    req.session.oauthState = state;

    // Request only the missing scope
    const params = new URLSearchParams({
      client_id:    process.env.MONEI_CLIENT_ID,
      redirect_uri: process.env.MONEI_REDIRECT_URI,
      scope:        'wallet:withdraw', // only what's missing
      state,
    });

    res.redirect(`https://monei.cc/connect/authorize?${params}`);
  });
  ```

  ```python Python theme={null}
  @app.get("/enable-withdrawals")
  def enable_withdrawals():
      state = secrets.token_hex(16)
      session["oauth_state"] = state

      from urllib.parse import urlencode
      params = urlencode({
          "client_id":    CLIENT_ID,
          "redirect_uri": REDIRECT_URI,
          "scope":        "wallet:withdraw",  # only what's missing
          "state":        state,
      })

      return redirect(f"https://monei.cc/connect/authorize?{params}")
  ```
</CodeGroup>

***

## What to do when a critical scope is missing

If your app cannot function at all without a scope the user declined:

1. Don't throw an error silently
2. Explain which feature is unavailable and why it needs that permission
3. Offer a clear path to re-authorize link to your `/enable-[feature]` route
4. Never call an endpoint the user hasn't granted access to. It returns `403` and erodes trust

***

<CardGroup cols={2}>
  <Card title="Scopes Reference" icon="key" href="/connect/scopes">
    Full table of every scope and what it unlocks
  </Card>

  <Card title="Token Management" icon="arrows-rotate" href="/connect/tokens">
    Refresh, store, and revoke access tokens
  </Card>
</CardGroup>
