# auth.md

You are an agent. JoAi credentials are OAuth 2.1 on `https://api.joai.ai`. Follow Discover, then Register or reuse a client, then Exchange, then call MCP or the API with the access_token. Handle Errors and Revocation when a call fails.

JoAi does **not** implement the WorkOS agentic-registration profile. There is no `agent_auth` block, no `identity_endpoint`, no `identity_assertion` exchange, no `service_auth` ceremony, and no `id-jag` grant. Do not invent those URLs. Use the endpoints in the live metadata below.

Product walkthrough: [API tokens](https://docs.joai.ai/integrations/api-tokens) and [MCP](https://docs.joai.ai/protocols/mcp).

## Discover

A 401 from MCP should carry `WWW-Authenticate` pointing at protected-resource metadata. If you do not have that header, fetch the metadata directly.

```http
GET https://joai.ai/.well-known/oauth-protected-resource
GET https://api.joai.ai/.well-known/oauth-protected-resource
```

The protected resource is `https://api.joai.ai/mcp`. `authorization_servers` is `https://api.joai.ai`. Scopes are `mcp:read`, `mcp:write`, and `mcp:use`. `bearer_methods_supported` is `header`.

Then fetch authorization-server metadata:

```http
GET https://api.joai.ai/.well-known/oauth-authorization-server
```

Use only the fields that document is actually serving:

- `issuer` — `https://api.joai.ai`
- `authorization_endpoint` — `https://api.joai.ai/oauth/authorize`
- `token_endpoint` — `https://api.joai.ai/oauth/token`
- `registration_endpoint` — `https://api.joai.ai/oauth/register`
- `revocation_endpoint` — `https://api.joai.ai/oauth/token/revoke`
- `introspection_endpoint` — `https://api.joai.ai/mcp/introspect`
- `scopes_supported` — `mcp:read`, `mcp:write`, `mcp:use`
- `grant_types_supported` — `authorization_code`, `client_credentials`, `refresh_token`
- `code_challenge_methods_supported` — `S256` (prefer this; `plain` is also listed)

There is no `agent_auth` object. Ignore any template that tells you to POST an `identity_endpoint` or to mint an `id-jag`.

## Pick a method

1. **MCP client (Cursor, Claude, ChatGPT, and the same class of host)** — authorization code + PKCE (`S256`). This is the path to use. OAuth can create and name an agent if the user does not have one yet.
2. **A backend you operate** — `client_credentials` only if you already have a confidential client. Do not ask a human to paste a client secret into a chat.
3. **You already have a refresh token** — `refresh_token` at the token endpoint. Do not register again.

Do not pick `identity_assertion`, `service_auth`, or `id-jag`. JoAi will not accept them.

Prefer the smallest scope that can do the job: `mcp:read` for reads, `mcp:write` for mutating tools, `mcp:use` only when the client needs both.

## Register

Public MCP clients should use dynamic client registration:

```http
POST https://api.joai.ai/oauth/register
```

Send a JSON body with `client_name`, `redirect_uris`, and `token_endpoint_auth_method` of `none` for a public PKCE client. The response includes `client_id`. Store it. Do not register on every request.

Humans can also create an API token in the product. That path is documented at [API tokens](https://docs.joai.ai/integrations/api-tokens). An API token is not an OAuth access_token; send it the way that page says.

## Claim

JoAi has no claim ceremony, no user-code, and no `service_auth` email loop. The human consents in the browser at `authorization_endpoint`. If no agent exists yet, that login can create and name one. After consent you only have the authorization code. There is nothing to poll.

## Exchange

Send the authorization code to the token endpoint with PKCE.

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

grant_type=authorization_code&code=...&redirect_uri=...&client_id=...&code_verifier=...
```

The response is an `access_token`, usually a `refresh_token`, and `expires_in`. Keep the refresh token. Call the token endpoint again with `grant_type=refresh_token` before the access_token expires. Do not put the code verifier on any later call.

`client_credentials` uses the same token endpoint with the client secret via HTTP Basic or `client_secret_post`. Do not use that grant from a browser or a chat transcript.

## Use the access_token

Send `Authorization: Bearer <access_token>`.

- MCP: `https://cortex.joai.ai/mcp` (optional `?agent={agentUuid}`). Discover tools with `tools/list`. Do not hardcode a tool list.
- HTTP API: `https://api.joai.ai/api/v1`, described by [openapi.json](https://joai.ai/openapi.json).

Sensitive writes can return `pending: true`. Poll `check_warp_executions` until the warp succeeds, fails, or is declined. Do not retry the write while it is pending.

## Errors

- `401` with `WWW-Authenticate` — token missing, expired, or for the wrong resource. Discover again, then refresh or re-authorize. Do not loop register.
- `invalid_grant` / `invalid_client` from the token endpoint — the code, verifier, or client no longer matches. Start authorize again. Do not reuse an authorization code.
- `insufficient_scope` — ask for a narrower retry only if a higher scope is actually required. Prefer `mcp:read` or `mcp:write` over jumping to `mcp:use`.
- API error body shape is `{ "error": { "code": "...", "message": "..." } }` as in the OpenAPI spec.

## Revocation

Revoke a refresh token or access_token at the revocation endpoint. Do this when the user disconnects the client.

```http
POST https://api.joai.ai/oauth/token/revoke
Content-Type: application/x-www-form-urlencoded

token=...&token_type_hint=refresh_token
```

After revocation, discard both tokens. The next session starts at Discover. JoAi does not push an `identity_assertion` revocation event; there is no `agent_auth` events endpoint to subscribe to.
