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-serverA public document (RFC 8414). No credentials are required:
{
"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
- Start an HTTP listener on a free local port and generate a
code_verifierand itscode_challenge(S256). - 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- The administrator approves access on the consent screen (
POST /oauth/authorize, no body). The response redirects to your loopback address withcodeandstate. - Exchange the
codefor 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{
"token_type": "Bearer",
"access_token": "eyJhbGciOiJSUzI1NiIs...",
"refresh_token": "def50200f1b8f2e3...",
"expires_in": 3600
}- 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.
- 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:
X-API-Keyidentifies and validates the credential, as in autonomous mode. A revoked, suspended, or expired credential is rejected before theAuthorizationheader is examined.- The credential's
auth_modedetermines whether human mode is allowed. A credential withauth_modeset toautonomousreturns403 human_mode_not_allowed. - The bearer token must be a valid JWT (RS256 signature,
aud, expiration, not revoked). A failure never falls back to autonomous mode: the API returns401 invalid_access_token. - The token's administrator must belong to the same account as the credential: otherwise
403 tenant_mismatch. - 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:
{
"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 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.