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:

{
  "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
  1. The administrator approves access on the consent screen (POST /oauth/authorize, no body). The response redirects to your loopback address with code and state.
  2. 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
{
  "token_type": "Bearer",
  "access_token": "eyJhbGciOiJSUzI1NiIs...",
  "refresh_token": "def50200f1b8f2e3...",
  "expires_in": 3600
}
  1. 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.

  1. 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.
CodeStatusWhen
human_mode_not_allowed403A credential with auth_mode set to autonomous receives X-API-Key
human_context_required401A 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_token401The JWT has an invalid signature or aud, has expired, or was revoked
tenant_mismatch403The token's administrator belongs to a different account than the credential
admin_not_eligible403The token's administrator has no active link to the credential
ambiguous_credentials401A 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_revoked401The 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:

ActionEffect
Ending the administrator's session, removing the administrator's link to the credential, or removing the administratorEnds 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 credentialEnds 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.

On this page