# Incremental authorization

Your product will need a permission you did not ask for at first. That is expected, and it does not require a second connection or a lost grant.

## How to ask

Send the host through `/oauth/authorize` again with the **complete** scope set you want, not the difference.

```
scope=account:read listings:read calendar:read calendar:write rates:write
```

Absolute rather than incremental, because a delta requires you to know what you currently hold, and you may not: a host can narrow your access from their own account, and Haven staff can narrow your ceiling. The scope string you send is what the grant becomes.

Any scope you omit is removed. If you send only the new one, you will lose everything else.

## What the host sees

Three groups, in order: what they have already allowed, collapsed and de-emphasized; what is new, expanded and prominent; and what will be removed, with a warning.

Most implementations hide the third group. Hiding it is how a partner silently drops a host's `guests:read` and the host spends a week wondering where guest names went.

The host has to click, every time, even when you are asking for a subset of what they have already granted. An authorization endpoint that auto-approves is an endpoint any page on the internet can navigate a logged-in host's browser to.

## What happens to your existing tokens

**Narrowing takes effect immediately, and revokes nothing.** Because Haven recomputes effective scope on every request, a token minted with `calendar:write` against a grant that no longer includes it will refuse the next write with `insufficient_scope`, while everything still granted keeps working. Your integration degrades rather than dies, which is what the host intended when they narrowed it.

**Widening requires a new token.** Access tokens carry the scopes frozen at mint and never gain one. Complete the authorization flow and exchange the new code. A refresh cannot widen, because a refresh is bounded by the scopes on the token presenting it.

That gives one invariant worth building on:

> Scope can only ever be widened at `/oauth/authorize`, and that always passes through a human clicking approve.

## Handling insufficient_scope

A 403 for a missing scope carries what you need to fix it:

```json
{
  "success": false,
  "value": null,
  "error": {
    "detail": "insufficient_scope",
    "message": "This access token cannot perform \"create_price_override\": missing scope rates:write.",
    "requestId": "0f7e4c1a-2b8d-…",
    "data": {
      "missingScope": "rates:write",
      "grantedScopes": ["account:read", "listings:read", "calendar:read"],
      "reauthorizeUrl": "https://www.bookwithhaven.com/oauth/authorize?client_id=..."
    }
  }
}
```

`reauthorizeUrl` is prebuilt with the union of what you hold and what you were missing, so the fix is a redirect rather than a support ticket. It still needs your PKCE parameters and `state` appended; treat it as the scope list worked out for you, not a finished URL.

## When staff narrow your ceiling

Haven can reduce the maximum scope set your application may request. Existing grants are intersected against the new ceiling on the next request, so the effect is immediate and no tokens are invalidated.

If you attempt to authorize with a scope outside your ceiling, `/authorize` refuses with `invalid_scope` naming it, rather than quietly issuing a narrower grant. A silent narrowing at authorization time would leave you believing you had a permission you did not, which surfaces later as an inexplicable 403.
