Dynamic client registration
POST https://www.bookwithhaven.com/api/public/oauth/register
RFC 7591. An unauthenticated endpoint that mints a client_id on the spot, for clients that have no other way to obtain one.
This is for MCP clients. If you are building a server-side integration against the REST API, do not use it — apply at Getting access and a Haven staffer will issue you a reviewed client with a scope ceiling chosen for what you actually do. That path is unchanged and it is the one that gets you a named support contact, a rate-limit tier and a listing.
Use this endpoint when your software is handed a Haven MCP URL by its own user and has nowhere to get a client_id from. That is Claude, ChatGPT, an editor, the MCP Inspector, and anything else built on the MCP SDK's auth module — all of which discover this endpoint from the authorization-server metadata and call it without being told to.
The request
POST /api/public/oauth/register HTTP/1.1
Content-Type: application/json
{
"client_name": "Example Assistant",
"redirect_uris": ["https://example.com/oauth/callback"],
"token_endpoint_auth_method": "none",
"grant_types": ["authorization_code", "refresh_token"],
"response_types": ["code"],
"client_uri": "https://example.com",
"policy_uri": "https://example.com/privacy",
"tos_uri": "https://example.com/terms",
"software_id": "example-assistant",
"software_version": "2.4.0"
}
redirect_uris is the only required field. Everything else has a default, and the four *_uri fields must each be an absolute https URL if you send them at all.
Redirect URIs go through exactly the same validator a staff-registered client's do — see The authorize endpoint for the full set of rules. In short: https only, except http://127.0.0.1:<port> and http://[::1]:<port> for a native app binding a loopback port; no fragment, no wildcard, no userinfo, no ?next=-style forwarding parameter; and no custom scheme at all if you ask for a client secret. At most ten.
token_endpoint_auth_method may be none, client_secret_basic or client_secret_post. Send none unless your client genuinely runs on a server you control. PKCE with S256 is mandatory on this API for every client including confidential ones, so none is not the weaker option — it is the honest one for anything that ships to a user's machine.
The response
HTTP/1.1 201 Created
Content-Type: application/json
Cache-Control: no-store
{
"client_id": "hvci_9pQ2vX7mKd0bR4tYuN1sLa",
"client_id_issued_at": 1772668800,
"client_name": "Example Assistant",
"redirect_uris": ["https://example.com/oauth/callback"],
"grant_types": ["authorization_code", "refresh_token"],
"response_types": ["code"],
"token_endpoint_auth_method": "none",
"scope": "account:read calendar:read calendar:write …"
}
A client_secret and a client_secret_expires_at of 0 are included when you asked for a secret-bearing auth method. The secret is shown once; Haven stores only its hash.
scope is the ceiling — every scope this client is permitted to ask for at /oauth/authorize. It is the full grantable set, because no human chose a narrower one for you. It is not what you get: what you get is decided by the host on the consent screen and reported back in the scope field of the token response. Read that field, not this one.
Store the client_id and reuse it. Registering again mints a second, unrelated client, and a host who already connected the first will be asked to connect the second from scratch.
What a registered client can and cannot do
Registration mints an identifier. It grants nothing.
A dynamically registered client can complete the authorization flow immediately — there is no review queue and no waiting — and everything it can then do is bounded by a Haven host signing in and clicking "allow" on a screen that says, in as many words, that Haven has not reviewed the application. That is the trust decision, and self-registration moves it to the host rather than removing it.
Two consequences worth designing for:
- The connection is capped by the person who authorized it, not by what you asked for. A host whose own role on the account is Viewer grants you a read-only connection: the write scopes are dropped, the consent screen tells them so by name, and the token response reports the narrower set. Read
scopeon the token response andcapabilities.canWriteonGET /me, and render accordingly. - A host can narrow or disconnect at any time from their Haven account, and effective scopes are recomputed on every request.
tools/listandGET /operationsare the fastest way to see it.
Errors
RFC 7591 §3.2.2 shape, at HTTP 400:
{
"error": "invalid_redirect_uri",
"error_description": "\"http://example.com/cb\" cannot be registered: only https is allowed, except http on 127.0.0.1 or [::1] for a native application."
}
error | Means |
|---|---|
invalid_redirect_uri | A URI in redirect_uris failed validation, or there were none, or there were more than ten. The description names the URI and the rule. |
invalid_client_metadata | Any other field was the wrong type, too long, not https, or asked for a grant type this server does not implement. |
Registration is rate-limited by IP: twenty per hour, answered with 429 and a Retry-After. A real client registers once and reuses the client_id forever, so this is only reachable by a loop. If you hit it while developing, you are re-registering where you should be caching.
What Haven does not implement
RFC 7592 client configuration management. No registration_access_token and no registration_client_uri come back, so there is no GET, PUT or DELETE on a registered client. Those fields are optional in RFC 7591 and no MCP host uses them; issuing a management credential Haven does not honour would advertise a lifecycle that does not exist. Change your registration by registering again. Retire a connection by having the host disconnect it, or by revoking your token.
software_statement. A signed assertion is only worth reading if a key is on file, and nothing signs one today. Send it and it is ignored.