Developers

Administrators (OAuth)

An administrator calls the API as themself with an OAuth access token, sent alone in Authorization. The call acts as that administrator, with the administrator's own permissions, and needs no credential. 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-server

The 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 administrator and the account, 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

  1. Start an HTTP listener on a free loopback port, and generate a code_verifier, its code_challenge (S256), and a random state.

  2. 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=xyz123

The 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.

  1. After signing in on the platform, the administrator approves or denies access on the consent screen. Approving redirects the browser to your redirect_uri with code, state, and iss (RFC 9207). Denying redirects it with error=access_denied, state, and iss instead. Before you use the response, check that state equals the value you generated and that iss equals the discovery document's issuer.

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_reasonMeaning
mfa_requiredThe administrator does not have two-factor authentication turned on
account_not_eligibleThe account cannot authorize this client
platform_mismatchThe 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.

  1. Exchange the code for a token pair, with the same redirect_uri you 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.

  1. 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.

  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>

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 request as an administrator

Authorization: Bearer <access-token-JWT>

The bearer token must be a valid JWT (RS256 signature, aud, expiration, not revoked), and its administrator must still belong to the account. A failure never falls back to a credential: the API returns 401 invalid_access_token.

The request acts as the administrator, with the administrator's own permissions: 403 permission_denied without the permission, 404 not_found outside its scope, as for a credential. GET /capabilities lists the permissions in effect. Idempotency keys, operations, configuration plans, and event streams created by the administrator belong to that administrator. The audit trail shows the administrator in actor.admin_id, with no credential.

CodeStatusWhen
invalid_access_token401The JWT has an invalid signature or aud, has expired, or was revoked, or its administrator was removed, moved to another account, or had their access revoked
ambiguous_credentials401The access token is sent together with a credential secret, a header is duplicated, or an ID token is sent instead of an access token

The access token issued by cademi auth login and sent together with X-API-Key, as cademi-cli 0.3.x does, also returns 401 ambiguous_credentials. cademi-cli 0.4.0 and later send the access token alone; update the CLI with cademi update.

The User-Agent header never determines who the caller is.

No current credential

No credential authenticates a request made with an administrator's access token, so GET /credentials/current and GET /credentials/current/policies return 404 not_found. Use GET /capabilities to read what the administrator can do.

Revocation

ActionEffect
Removing the administrator, moving them to another account, or revoking their accessThe next request with their access token is rejected with 401 invalid_access_token.
Revoking a token with POST /oauth/revokeEnds that token pair.

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.

On this page