# Human mode (OAuth)

Human mode lets a call act on behalf of a specific administrator, on top of an existing API credential. It never replaces the credential: every human-mode call still sends `X-API-Key`, and the OAuth access token is added when the credential requires a person's identity. The OAuth endpoints are outside `/api/v3`, on the root Cademí host.

## Discovery

```
GET /.well-known/oauth-authorization-server
```

A public document (RFC 8414). No credentials are required:

```json
{
  "issuer": "https://api.cademi.com.br",
  "authorization_endpoint": "https://api.cademi.com.br/oauth/authorize",
  "token_endpoint": "https://api.cademi.com.br/oauth/token",
  "revocation_endpoint": "https://api.cademi.com.br/oauth/revoke",
  "response_types_supported": ["code"],
  "grant_types_supported": ["authorization_code", "refresh_token"],
  "code_challenge_methods_supported": ["S256"],
  "token_endpoint_auth_methods_supported": ["none"]
}
```

## Client and PKCE

The public client is `cademi-cli`. It has no client secret and requires PKCE with `S256`: a request with `code_challenge_method=plain` is rejected before any other validation. The `redirect_uri` must be a loopback address, `http://127.0.0.1:{port}/callback` or `http://[::1]:{port}/callback`, on any free port. The host and path must match exactly. Any other host, including internal addresses, or any other path is rejected.

## Authorization Code flow

1. Start an HTTP listener on a free local port and generate a `code_verifier` and its `code_challenge` (S256).
2. Open the authorization URL in the browser. The administrator must already be signed in to Cademí with two-factor authentication completed:

```
GET /oauth/authorize
  ?response_type=code
  &client_id=cademi-cli
  &redirect_uri=http://127.0.0.1:51789/callback
  &code_challenge=E9Melhorexemplo9M2sj...
  &code_challenge_method=S256
  &state=xyz123
```

3. The administrator approves access on the consent screen (`POST /oauth/authorize`, no body). The response redirects to your loopback address with `code` and `state`.
4. Exchange the `code` for a token pair:

```
POST /oauth/token
Content-Type: application/x-www-form-urlencoded

grant_type=authorization_code
&client_id=cademi-cli
&code=<code received in the redirect>
&code_verifier=<verifier generated in step 1>
&redirect_uri=http://127.0.0.1:51789/callback
```

```json
{
  "token_type": "Bearer",
  "access_token": "eyJhbGciOiJSUzI1NiIs...",
  "refresh_token": "def50200f1b8f2e3...",
  "expires_in": 3600
}
```

5. Refresh the tokens with the refresh token. The administrator does not need to approve access again:

```
POST /oauth/token
Content-Type: application/x-www-form-urlencoded

grant_type=refresh_token
&client_id=cademi-cli
&refresh_token=<current refresh token>
```

Refresh tokens rotate: each exchange invalidates the refresh token you used and returns a new one. Presenting a refresh token that was already exchanged is treated as reuse, a sign that the token leaked, and revokes the entire token family: every access token and refresh token issued to that administrator and client, not only the token presented.

6. Revoke a token explicitly (RFC 7009). The same field accepts an access token or a refresh token:

```
POST /oauth/revoke
Content-Type: application/x-www-form-urlencoded

token=<access_token or refresh_token>
```

The endpoint always returns `200` with an empty body, even for unknown or already revoked tokens, so it cannot be used to check whether a token is valid.

## Access token format

The access token is a JWT signed with RS256. Its `aud` is `cademi-api-v3` (never the `cademi-cli` client ID), and its `sub` is the ID of the administrator who owns the token. Access tokens are valid for 1 hour. Refresh tokens are valid for 30 days.

## Sending a human-mode request

```
Authorization: Bearer <access-token-JWT>
X-API-Key: <credential-secret>
```

The API always checks a human-mode request in the same order, and each step must pass before the next one runs:

1. `X-API-Key` identifies and validates the credential, as in autonomous mode. A revoked, suspended, or expired credential is rejected before the `Authorization` header is examined.
2. The credential's `auth_mode` determines whether human mode is allowed. A credential with `auth_mode` set to `autonomous` returns `403 human_mode_not_allowed`.
3. The bearer token must be a valid JWT (RS256 signature, `aud`, expiration, not revoked). A failure never falls back to autonomous mode: the API returns `401 invalid_access_token`.
4. The token's administrator must belong to the same account as the credential: otherwise `403 tenant_mismatch`.
5. The administrator must have an active link to the credential: otherwise `403 admin_not_eligible`.

| Code | Status | When |
|---|---|---|
| `human_mode_not_allowed` | 403 | A credential with `auth_mode` set to `autonomous` receives `X-API-Key` |
| `human_context_required` | 401 | A credential that requires human mode (`human_required`) is used without an access token, or `X-API-Key` is sent without a bearer token |
| `invalid_access_token` | 401 | The JWT has an invalid signature or `aud`, has expired, or was revoked |
| `tenant_mismatch` | 403 | The token's administrator belongs to a different account than the credential |
| `admin_not_eligible` | 403 | The token's administrator has no active link to the credential |
| `ambiguous_credentials` | 401 | A header is duplicated, an ID token is sent instead of an access token, or the credential secret is sent as a bearer token together with `X-API-Key` |
| `credential_revoked` | 401 | The credential itself is revoked, which ends both modes at once |

The `User-Agent` header never determines the mode.

## Current credential in human mode

In human mode, `GET /credentials/current` also returns the administrator behind the call:

```json
{
  "data": {
    "object": "credential",
    "id": "key_01J8ZQZQZQZQZQZQZQZQZQZQZQ",
    "auth_mode": "both",
    "human": {
      "admin_id": "adm_42",
      "expires_at": "2026-09-23T15:04:00.000Z",
      "eligible_since": "2026-08-01T09:00:00.000Z"
    }
  }
}
```

`human` is `null` in autonomous mode. See [Credentials](https://cademi.dev/api/reference/credentials.md) for the full schema.

## Revocation

Revoking an administrator's access and revoking the credential have different effects:

| Action | Effect |
|---|---|
| Ending the administrator's session, removing the administrator's link to the credential, or removing the administrator | Ends human mode only. The next human-mode request is rejected with `admin_not_eligible` or `tenant_mismatch`. A credential with `auth_mode` set to `both` still authenticates on its own in autonomous mode, and an event stream already opened by that credential is not affected. |
| Revoking the credential | Ends both modes at once. Open event streams for that credential are closed shortly afterward. |

## Out of scope

This version does not issue ID tokens and does not support OpenID Connect: the discovery document advertises neither `userinfo_endpoint` nor the `openid` scope.
