The authorize endpoint
GET /oauth/authorize is a browser page, not an API call. Open it in the host's browser or system webview. Do not fetch it.
Parameters
All of these are required.
| Parameter | Value |
|---|---|
response_type | code |
client_id | Your hvci_... identifier |
redirect_uri | One of your registered URIs, byte for byte |
scope | Space-separated scope names |
state | Opaque value you generate and verify on return, 512 characters or fewer |
code_challenge | Base64url SHA-256 of your verifier, 43 characters, unpadded |
code_challenge_method | S256 |
Success
Haven redirects to your redirect_uri with code, state and iss appended. The code is single-use and expires in sixty seconds.
Failures split two ways, and the split matters
If Haven cannot establish that your redirect target is trustworthy, it renders an error page on its own domain and does not redirect. That covers a missing or unknown client_id, an application that is not approved, a missing redirect_uri, and a redirect_uri that does not match a registered value. Redirecting an error to an unvalidated URI is how an authorization server becomes an open redirector, so Haven declines to.
If the redirect target is validated and something else is wrong, Haven redirects with error, error_description, state and iss in the query string. That covers a bad response_type, a missing or plain PKCE challenge, an unknown scope, a scope outside your approved ceiling, a host whose seat is read-only, and the host declining.
Codes you will see in the second case: invalid_request, unsupported_response_type, invalid_scope, access_denied, server_error, temporarily_unavailable.
An unknown scope is refused rather than dropped. If you send calendar:wrote, you get invalid_scope naming it. Silently ignoring the typo would hand you a grant that does less than your code expects, and you would discover it in production.
Redirect URI rules
These are strict, and the strictness is the point. A permissive redirect matcher is the most common serious flaw in an OAuth deployment.
At registration, a URI must be https, must parse to exactly what you typed, must carry no fragment, no userinfo, no wildcard and no encoded traversal, and must not use punycode. Haven normalizes nothing: if the parser would rewrite your URI, registration is refused and you are shown the canonical form to register instead. That is what makes exact matching safe at request time.
Two exceptions exist for native and local development. http://127.0.0.1 and http://[::1] may be registered with any path. Custom schemes are accepted for public clients only, and must be reverse-DNS with at least one dot: com.yourcompany.app:/callback is fine, yourapp:/callback is not, because a single-label scheme is first come, first served on the operating system and any other installed app can claim it.
localhost is not accepted over http. Use the literal 127.0.0.1. This trips up almost everyone, and the reason is that localhost resolves through the OS resolver and can be repointed by a hosts file or a hostile DNS server, while the literal cannot.
At request time, the presented URI must equal a registered one byte for byte. Case, trailing slashes, default ports and query strings are all part of the string. The one exception: for a registered loopback URI, the port on the presented URI is ignored, because a native app binds an ephemeral port it cannot know in advance. Everything else about it still has to match.
What the host sees
The consent screen names your application, shows your logo, and states in plain text which domain they will be sent to after approving. It names the account being connected and their role in it. Each scope you requested appears as a sentence describing what it allows, drawn from the same registry the server enforces, so the screen cannot promise something different from what the token will do.
Restricted scopes are never pre-selected. The host has to tick each one individually, and no "allow all" affordance covers them.
Two things worth designing for. The grant covers every property on the account; there is no per-property authorization. And a host on a read-only seat cannot approve write scopes at all, so the screen refuses rather than issuing a grant that would fail on first use.