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:
{
"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.