# Kite — agent authentication (auth.md)

How an agent gets a credential for Kite's MCP server: OAuth 2.1, or an API key the user makes.
This follows the auth.md spec (https://workos.com/auth-md) in its `register_uri`/`claim_uri` form.

## Discover

1. Call the MCP server without a credential: `POST https://kite.new/api/mcp`. It answers 401 with
   `WWW-Authenticate: Bearer resource_metadata="https://kite.new/.well-known/oauth-protected-resource/api/mcp"`.
2. GET that document (RFC 9728): `resource` is https://kite.new/api/mcp, `authorization_servers` is [https://kite.new].
3. GET https://kite.new/.well-known/oauth-authorization-server (RFC 8414): the OAuth endpoints and the
   `agent_auth` block:

```json
{ "agent_auth": {
    "skill": "https://kite.new/auth.md",
    "register_uri": "https://kite.new/api/oauth/register",
    "claim_uri": "https://kite.new/oauth/authorize",
    "identity_types_supported": ["anonymous"],
    "anonymous": { "credential_types_supported": ["access_token"], "claim_uri": "https://kite.new/oauth/authorize" } } }
```

## Pick a method

- **anonymous → access_token**: register as an OAuth client with no identity, then the user claims
  you by signing in to Kite and approving you. For agents that can open a browser for the user
  (Claude, ChatGPT, Cursor, Claude Code).
- **API key**: the user makes one at https://kite.new/settings?tab=agents and gives it to you. It carries
  their identity. For headless use; store it as KITE_API_KEY and never print it back.

`identity_assertion` (an `urn:ietf:params:oauth:token-type:id-jag` id-jag) is not supported:
Kite has no identity or token-exchange endpoint.

## Register

RFC 7591 dynamic client registration at the `register_uri`, no secret needed:

```
POST https://kite.new/api/oauth/register
{"client_name":"My Agent","redirect_uris":["https://example.com/callback"]}
→ {"client_id":"…","token_endpoint_auth_method":"none","grant_types":["authorization_code","refresh_token"]}
```

Redirect URIs must be https, http on a loopback address, or an app scheme. `client_name` is the
name the user sees on the approval screen and on the board while you work.

## Claim

The user claims you at the `claim_uri`, the authorization endpoint, with PKCE (S256):

```
GET https://kite.new/oauth/authorize?response_type=code&client_id=…&redirect_uri=…&scope=pages&code_challenge=…&code_challenge_method=S256
```

They sign in (or sign up) and approve you; the code that comes back to your redirect URI is bound to
their account. An API key is claimed already: it belongs to the user who made it.

## Use the credential

```
POST https://kite.new/api/oauth/token  grant_type=authorization_code&code=…&redirect_uri=…&code_verifier=…&client_id=…
→ {"access_token":"kot_…","token_type":"Bearer","expires_in":604800,"refresh_token":"kor_…","scope":"pages"}
```

Send `Authorization: Bearer <access_token or API key>` on every MCP request to https://kite.new/api/mcp
(Streamable HTTP). Refresh with `grant_type=refresh_token` before the 7 days are up; refresh
tokens last 90 days and rotate on every use. What to do next: https://kite.new/skill.md.

## Errors

- **401** on /api/mcp: no credential, or it expired or was revoked. The response carries
  `WWW-Authenticate`; refresh once, and if that fails start again at Discover.
- **400** `invalid_redirect_uri` or `invalid_client_metadata` from register; `invalid_grant` from
  token (code used or expired, PKCE failed, refresh token rotated away).
- **429**: registration and agent calls are rate-limited; back off.

## Revocation

- **You**: POST `token=…` to https://kite.new/api/oauth/revoke (RFC 7009). It always answers 200.
- **The user**: Settings → Agents (https://kite.new/settings?tab=agents) disconnects an app or revokes a
  key. Its tokens then answer 401 / `invalid_grant`.
- **Reuse**: a refresh token used after it was rotated away revokes the whole connection.
