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 sends the credential secret in X-API-Key and the administrator's OAuth access token in Authorization. The OAuth endpoints are outside /api/v3. Discovery, token exchange, and revocation are served at https://api.cademi.com.br. Authorization starts there and continues on the platform's own address.
Discovery
GET /.well-known/oauth-authorization-serverThe discovery document (RFC 8414) is public and requires no credentials:
{
"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"],
"authorization_response_iss_parameter_supported": true
}Using the CLI
To install the cademi CLI and keep it up to date, see Installation. Signing in, profiles, and credentials in the CLI are covered in Authentication.
cademi auth login runs this flow for you. It listens on a loopback address and opens the authorization page, where the administrator chooses the platform, signs in, and approves access. With --platform <address>, the CLI sends the platform parameter to skip the selector and saves the address in its profile. It checks that the returned iss matches the issuer, then exchanges the code with PKCE, stores the tokens in the system keychain, and refreshes them on its own. cademi auth status shows the current mode and administrator, and cademi auth logout revokes the tokens. The rest of this page describes the protocol for clients that implement it themselves.
Client and PKCE
The public client is cademi-cli. It has no client secret, so every authorization request must use PKCE with code_challenge_method=S256. A request without code_challenge or code_challenge_method, or with any other method, including plain, is rejected with invalid_request.
The redirect_uri must be http://127.0.0.1:{port}/callback or http://[::1]:{port}/callback, on any free port. Any other scheme, host, or path, including localhost and private network addresses, is rejected with 401.
Choosing a platform
The authorization endpoint, https://api.cademi.com.br/oauth/authorize, opens with a platform selector. The administrator enters the address of the Cademí account they administer: its subdomain (acme or acme.cademi.com.br) or its custom domain, when that domain is active. A full URL, such as https://acme.cademi.com.br, is also accepted. The selector remembers the last platform each browser was sent to and offers it first on the next visit, so a returning administrator does not have to type the address again.
To skip the selector, add platform=<address> to the authorization URL, for example platform=acme.cademi.com.br or platform=acme. If the address matches the last platform that browser was sent to, the browser goes directly to that platform. Otherwise, a confirmation screen first shows the account's name and address, and the administrator confirms before continuing. An address that matches no account opens the selector with an error, so the administrator can correct it.
The browser is then redirected to the platform's own authorization page, https://<platform>/oauth/authorize, with the same OAuth parameters. This address is the account's custom domain when that domain is active, and its subdomain address, such as acme.cademi.com.br, otherwise. There, the administrator signs in, completes two-factor authentication, and reaches the consent screen, which shows the account's name and branding. The administrator must have two-factor authentication turned on. An administrator with a pending two-factor challenge stays on that challenge until it is completed; the other cases where the administrator cannot authorize are described in step 3.
Authorization Code flow
-
Start an HTTP listener on a free loopback port, and generate a
code_verifier, itscode_challenge(S256), and a randomstate. -
Open the authorization URL in the browser:
GET https://api.cademi.com.br/oauth/authorize
?response_type=code
&client_id=cademi-cli
&redirect_uri=http://127.0.0.1:51789/callback
&code_challenge=E9Melhoa2OwvFrEMTJguCHaoeK1t8URWbuGJSstw-cM
&code_challenge_method=S256
&state=xyz123The authorization endpoint validates the request before it shows any page. If the client_id or redirect_uri is invalid, the browser shows an error page and is not redirected, because the redirect address cannot be trusted. If both are valid but PKCE is missing or does not use S256, the browser is redirected to your redirect_uri with error=invalid_request, error_description, state, and iss. Otherwise, the browser shows the platform selector described in Choosing a platform.
- After signing in on the platform, the administrator approves or denies access on the consent screen. Approving redirects the browser to your
redirect_uriwithcode,state, andiss(RFC 9207). Denying redirects it witherror=access_denied,state, andissinstead. Before you use the response, check thatstateequals the value you generated and thatissequals the discovery document'sissuer.
When the signed-in administrator cannot authorize a valid request, the browser is also redirected to your redirect_uri with error=access_denied, state, and iss, plus an error_reason and an error_description in the platform's language:
error_reason | Meaning |
|---|---|
mfa_required | The administrator does not have two-factor authentication turned on |
account_not_eligible | The account cannot authorize this client |
platform_mismatch | The administrator belongs to a different account than the platform in the request |
An access_denied without error_reason means the administrator chose Deny on the consent screen. Branch on error_reason, not on error_description, which is written for people. A request that the platform cannot validate shows an error page and is not redirected.
- Exchange the
codefor a token pair, with the sameredirect_uriyou sent in step 2:
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
}The code can be used only once and expires 10 minutes after it is issued.
- Renew the tokens with the refresh token, without asking the administrator 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 returns a new pair and invalidates the refresh token you used. Presenting a refresh token that was already exchanged is treated as reuse, a sign that the token leaked. Reuse revokes the entire token family: every access token and refresh token issued to that administrator for the same client, in any session.
- 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>Revoking one token of a pair also revokes the other. The endpoint always returns 200, 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 iss equals the discovery document's issuer, its aud is cademi-api-v3 (never the cademi-cli client ID), and its sub identifies the administrator who owns the token. Access tokens are valid for 1 hour, and refresh tokens 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 |
|---|---|
| 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. |
Signing out of the Cademí dashboard does not revoke the access token or the refresh token: it only ends the browser session. The tokens remain valid until they expire or are explicitly revoked, so a call made shortly after signing out still succeeds.
Out of scope
The authorization server does not issue ID tokens and does not support OpenID Connect: the discovery document advertises neither userinfo_endpoint nor the openid scope.