Skip to main content
This page is for people building an MCP client. If you only want to connect an existing app, see Claude, ChatGPT or other MCP apps. Keebai implements the MCP authorization flow: OAuth 2.1 authorization code with PKCE, protected resource metadata (RFC 9728), authorization server metadata (RFC 8414), client ID metadata documents and dynamic client registration (RFC 7591), resource indicators (RFC 8707), and revocation (RFC 7009). The OAuth and .well-known endpoints sit at the root of api.keebai.com, not under /v1. The metadata and signing-key endpoints answer CORS requests from any origin. The token and revocation endpoints answer them only for public clients, and only from the origin of one of the client’s registered redirect URIs.

Discovery

1

Call the server without a token

POST /v1/mcp without Authorization, or with an invalid or expired token, returns 401 with a pointer to the resource metadata:
2

Read the protected resource metadata

GET https://api.keebai.com/.well-known/oauth-protected-resource/v1/mcp (also served at /.well-known/oauth-protected-resource):
3

Read the authorization server metadata

GET https://api.keebai.com/.well-known/oauth-authorization-server (the same document is served at /.well-known/openid-configuration):
Only the fields that matter for an MCP client are shown.
scopes_supported lists every scope a connection can be granted: the active scopes that belong to a permission group, plus offline_access. The user approves them in groups, not one by one. Both documents read the list from the same catalog, which is cached for up to one minute.

Identify your client

You have two options. Neither needs anyone at Keebai to do anything.
Host a JSON document over HTTPS and use its URL as client_id. Keebai fetches it when the user starts the flow.
  • client_id inside the document must equal the URL it is served from, and redirect_uris is required.
  • The client is public: set token_endpoint_auth_method to none. Shared-secret methods, client_secret and client_secret_expires_at are rejected in a metadata document.
  • The URL must be https on the default port, with a domain name as host. IP addresses, localhost and hosts ending in .local, .localhost, .internal, .lan or .home.arpa are rejected without being fetched, and a host that resolves to a private or special-use address is refused when Keebai connects.
  • The document must answer 200 within 2.5 seconds and weigh at most 5 KB. Redirects are not followed.
  • client_uri, logo_uri and the other URL fields must be https.
  • Keebai caches the document for the max-age of its Cache-Control header, between 30 seconds and one day; without the header, for 30 seconds.

Redirect URIs

  • Matching is exact, character for character. The one exception is loopback: any port is accepted, as RFC 8252 requires for native apps. Register the URI with or without a port and use whichever port you get at runtime.
  • http is allowed only for loopback hosts.
  • A private-use scheme must be in reverse-domain form (com.example.agent). cursor: is the only other scheme accepted.
  • javascript:, data:, file:, blob:, about:, vbscript:, ftp:, ws: and wss: are always rejected.
A redirect URI outside these rules fails registration with 400 invalid_redirect_uri.

Authorize

Send the user’s browser to:
PKCE with S256 is mandatory for every client, public or confidential. offline_access is accepted and ignored. Whether you get a refresh token depends only on the client allowing the refresh_token grant, which it does by default. Keebai sends the browser to accounts.keebai.com, where the user signs in, chooses a company and approves permission groups. The granted scopes are the scopes your request asked for that belong to an approved group and that the user’s role allows. The user has 10 minutes to finish.

The redirect back

On approval:
When the user cancels:
Check iss against https://api.keebai.com before using the code (RFC 9207). It is always present, on success and on error. If the client_id or the redirect_uri is invalid — an unknown client, a metadata document that cannot be fetched or fails validation, a redirect URI that is not registered — Keebai does not redirect to your client, because it cannot trust the address. The user sees an error page on accounts.keebai.com instead. Every other error goes back to your redirect_uri with error set. The code is single-use and expires after 60 seconds.

Exchange the code

POST https://api.keebai.com/oauth/token, as application/x-www-form-urlencoded.
redirect_uri must be the same one you sent to /oauth/authorize, and resource, if sent, must be the MCP resource. Confidential clients from dynamic registration also authenticate, with HTTP Basic (client_secret_basic) or client_secret in the body (client_secret_post), whichever they registered.
200 OK
The response carries Cache-Control: no-store. Read scope to know what the user actually granted — it can be less than you asked for. Then call the server:
The access token is accepted only on /v1/mcp. The rest of the public API rejects it with 401; use a Personal Access Token there.

Refresh

The response has the same shape, with a new access token and a new refresh token. scope is optional and can only narrow what the connection already has.
Refresh tokens rotate on every use. Store the new one and discard the old one. If a refresh token that was already used comes back, Keebai treats it as stolen: it revokes the whole chain of refresh tokens from that sign-in and answers invalid_grant. The user has to sign in again. Access tokens already issued keep working until they expire, at most 15 minutes later.
If two parts of your client refresh at the same time, one of them presents an already-used token and both lose the connection. Serialize refreshes. Every refresh checks the user again. If their account was deactivated, or they no longer have access to the company of the connection, the refresh fails with invalid_grant.

Tokens

The connection itself lasts one year from the user’s last approval. After that, refreshing fails with invalid_grant and the user signs in again. The access token is a JWT for a single audience, https://api.keebai.com/v1/mcp, issued by https://api.keebai.com and signed with a key published at /oauth/jwks. It carries sub (the Keebai user), client_id, scope, company, project_id, email, grant_id (the connection), jti, iat and exp. Your client does not need to decode it — treat it as opaque, and read the granted scopes from the token response. Keebai does not store access tokens, so they cannot be revoked one by one: ending a connection stops refreshes, and the access token still in hand expires on its own.

Revoke

POST https://api.keebai.com/oauth/revoke with token=<refresh token>, as application/x-www-form-urlencoded, authenticating the same way as on the token endpoint. Revoking a refresh token ends the whole connection: every refresh token issued from that sign-in stops working.
The answer is 200 with an empty body, whether or not the token existed. Call it when the user disconnects in your app. Access tokens are JWTs and cannot be revoked here — sending one returns 400 unsupported_token_type. The one in hand expires within 15 minutes. Users can also disconnect from the portal, under Developers › Connected apps (Desarrolladores › Apps conectadas). That deletes the connection with its refresh tokens: the next refresh fails with invalid_grant, and the current access token keeps working until it expires, at most 15 minutes later.

Errors

Authorize

Errors that Keebai can send back to your client arrive on the redirect_uri as error and error_description query parameters, with state and iss. An unknown client_id, a metadata document that cannot be fetched or is invalid, and an unregistered redirect_uri are shown to the user on an error page at accounts.keebai.com. Your client receives nothing.

Token, registration and revocation

Errors are JSON, following RFC 6749:

On /v1/mcp

A tool call that lacks a scope does not change the HTTP status: it returns 200 with a tool result carrying isError: true and INSUFFICIENT_SCOPE, as described in how scopes are enforced.

What a connection can reach

  • One company. The user picks it on the consent screen. Like a Personal Access Token, the token decides the tenant, and nothing your client sends can change it. Writes go to the company’s default project.
  • The user’s own reach. The groups and scopes the user can approve are limited by their Keebai role.
  • One connection per client, user and company. If the same user authorizes your client again for the same company, the new approval replaces the previous scopes.