# auth.md

You are an agent. GitCafe supports **agentic registration** (auth.md v0.6.0):
discover → register → claim → exchange for an access_token → call API → handle
revocation. Follow the steps in order; do not skip ahead.

The resource server is https://git.cafe/api and the authorization server is https://git.cafe.
Registration creates no account or repository; a verified user approves
existing repositories. For longer access, use OAuth authorization code with PKCE
against the same resource, or the MCP server at https://git.cafe/mcp. Compare the
options at https://git.cafe/docs/agent-workflows.md#choose-how-to-authenticate.

## Step 1 — Discover

A 401 from the API carries the PRM URL:

```http
HTTP/1.1 401 Unauthorized
WWW-Authenticate: Bearer resource_metadata="https://git.cafe/.well-known/oauth-protected-resource/api"
```

Pull the `resource_metadata` value from that header and fetch it (1a). If you
don't have the 401 in hand, the conventional path for this resource is
https://git.cafe/.well-known/oauth-protected-resource/api (RFC 9728 path insertion; the
resource has a path).

### 1a. Fetch the Protected Resource Metadata

GET https://git.cafe/.well-known/oauth-protected-resource/api for resource, resource_name,
resource_logo_uri, authorization_servers, scopes_supported and
bearer_methods_supported.

### 1b. Fetch the Authorization Server metadata

GET https://git.cafe/.well-known/oauth-authorization-server for issuer, token_endpoint,
revocation_endpoint, grant_types_supported and agent_auth (skill,
identity_endpoint, claim_endpoint, identity_types_supported). Structured
metadata is authoritative.

## Step 2 — Pick a method

GitCafe supports one method: **service_auth** (you have the user's email; claim
ceremony required). anonymous returns anonymous_not_enabled and
identity_assertion returns issuer_not_enabled.

## Step 3 — Register

Before registering, surface resource_name, resource_logo_uri and the scopes to
the user and confirm.

### service_auth

```http
POST https://git.cafe/api/agent/identity
Content-Type: application/json

{"type":"service_auth","login_hint":"user@example.com"}
```

Response (200): registration_id, registration_type=service_auth, claim_url,
claim_token, claim_token_expires, post_claim_scopes, and claim with user_code,
expires_in (600), verification_uri and interval (5). No identity_assertion yet.
claim_token is returned exactly once; hold it in memory for the ceremony only.

## Step 4 — Claim ceremony

### 4a. Get the ceremony materials

They are in the claim block of the Step 3 response.

### 4b. Hand off to the user

Surface verification_uri and user_code in one message. The user opens the link,
signs in or signs up, verifies their email (it must match login_hint), types the
six-digit code into GitCafe (never back to you), then selects repositories and
permissions. GitCafe sends no email.

### 4c. Poll for completion

```http
POST https://git.cafe/api/oauth/token
Content-Type: application/x-www-form-urlencoded

grant_type=urn:workos:agent-auth:grant-type:claim&claim_token=<claim_token>
```

authorization_pending: keep polling at interval. slow_down: add five seconds.
On success: access_token, token_type=Bearer, expires_in (at most 900), scope,
resource, identity_assertion and assertion_expires.

The user_code and the registration expire together (claim_token_expires), so
expired_token means the registration is gone: POST claim_token and email to
claim_url returns 410 claim_expired; restart at Step 3.

## Step 5 — Exchange the assertion

```http
POST https://git.cafe/api/oauth/token
Content-Type: application/x-www-form-urlencoded

grant_type=urn:ietf:params:oauth:grant-type:jwt-bearer&assertion=<identity_assertion>&resource=https://git.cafe/api
```

Returns access_token, token_type, expires_in and scope. The assertion is
reusable until it expires, at most one hour after approval and 100 issuances.
invalid_grant: restart at Step 3; the user consents again.

## Step 6 — Use the access_token

```http
GET https://git.cafe/api/...
Authorization: Bearer <access_token>
```

Tokens work on eligible repository REST operations for the selected
repositories only, not MCP or native Git, and cannot create accounts,
repositories or credentials. No refresh_token is issued; re-run Step 5. On a
401 for a previously working token, try Step 5 once, then restart at Step 1.

Scopes use kind:family:level; read inspects, write changes, admin administers:
- `issue:contents:read`
- `issue:contents:write`
- `issue:contents:admin`
- `issue:labels:write`
- `issue:assignees:write`
- `issue:milestones:write`
- `issue:state:write`
- `issue:edit:write`
- `issue:lock:write`
- `issue:links:write`
- `issue:pin:write`
- `repo:contents:read`
- `repo:contents:write`
- `repo:contents:admin`
- `repo:metadata:read`
- `repo:metadata:write`
- `repo:metadata:admin`
- `repo:administration:read`
- `repo:administration:write`
- `repo:administration:admin`
- `repo:rulesets:read`
- `repo:rulesets:write`
- `repo:rulesets:admin`
- `repo:webhooks:read`
- `repo:webhooks:write`
- `repo:webhooks:admin`
- `repo:attachments:read`
- `repo:attachments:write`
- `repo:attachments:admin`
- `repo:settings:read`
- `repo:settings:write`
- `repo:settings:admin`
- `repo:visibility:read`
- `repo:visibility:write`
- `repo:visibility:admin`
- `repo:collaborators:read`
- `repo:collaborators:write`
- `repo:collaborators:admin`
- `repo:default-branch:read`
- `repo:default-branch:write`
- `repo:default-branch:admin`
- `repo:transfer:read`
- `repo:transfer:write`
- `repo:transfer:admin`
- `repo:catalog:read`
- `repo:catalog:write`
- `repo:catalog:admin`
- `repo:ci:read`
- `repo:ci:write`
- `repo:ci:admin`
- `repo:mirrors:read`
- `repo:mirrors:write`
- `repo:mirrors:admin`
- `repo:maintenance:read`
- `repo:maintenance:write`
- `repo:maintenance:admin`
- `repo:push-recovery:read`
- `repo:push-recovery:write`
- `repo:push-recovery:admin`
- `repo:sync:read`
- `repo:sync:write`
- `repo:sync:admin`
- `repo:stacks:read`
- `repo:stacks:write`
- `repo:stacks:admin`
- `repo:checks:read`
- `repo:checks:write`
- `repo:checks:admin`
- `pr:contents:read`
- `pr:contents:write`
- `pr:contents:admin`
- `pr:merge:write`
- `pr:review:read`
- `pr:review:write`
- `pr:labels:write`
- `pr:assignees:write`
- `pr:milestones:write`
- `pr:state:write`
- `pr:edit:write`
- `pr:lock:write`
- `pr:verdict:write`
- `pr:dismiss:write`
- `pr:reviewers:write`
- `comment:contents:read`
- `comment:contents:write`
- `comment:moderation:read`
- `comment:moderation:write`
- `comment:triage:read`
- `comment:triage:write`
- `branch:contents:read`
- `branch:contents:write`
- `branch:contents:admin`
- `branch:rulesets:read`
- `branch:rulesets:write`
- `branch:rulesets:admin`

## Errors

| Code | Where | What to do |
| --- | --- | --- |
| anonymous_not_enabled | /agent/identity | Use service_auth. |
| issuer_not_enabled | /agent/identity | No trusted providers; use service_auth. |
| invalid_login_hint | /agent/identity | Send an email address. |
| invalid_request | /agent/identity | Fix the body. |
| rate_limited (429) | /agent/identity | 100/hour per instance, 10/hour per address, 5/hour per email; back off. |
| invalid_claim_token | /agent/identity/claim | Restart at Step 3. |
| claimed_or_in_flight | /agent/identity/claim | The current user_code is still valid; keep polling. |
| claim_expired (410) | /agent/identity/claim | Restart at Step 3. |
| authorization_pending | token_endpoint | Keep polling. |
| slow_down | token_endpoint | Add five seconds to interval. |
| expired_token | token_endpoint | Registration expired; restart at Step 3. |
| access_denied | token_endpoint | Stop; the user declined. |
| invalid_grant | token_endpoint | Restart at Step 3. |
| invalid_target | token_endpoint | Use resource=https://git.cafe/api. |
| unsupported_grant_type | token_endpoint | Use one of the two grants above. |

Codes lock after five wrong entries. Registration never reveals whether an
account exists. 5xx: back off and retry; 4xx: do not retry the same payload.

## Revocation

POST token=<access_token>&token_type_hint=access_token (form encoded) to
revocation_endpoint to revoke one access_token; the assertion stays usable. The
user can revoke the whole registration in Settings → Authorized Apps, which
invalidates its tokens and assertion; you see invalid_grant at Step 5.

Pricing: https://git.cafe/pricing
Terms: https://git.cafe/legal/terms
Privacy: https://git.cafe/legal/privacy
Integration help: https://git.cafe/docs/account/mcp-connections.md#connect-an-agent-using-authmd
