OAuth Token Revocation
Revoke a previously issued access token via RFC 7009.
Overview
CAIRL's OAuth provider implements RFC 7009 — OAuth 2.0 Token Revocation. Partners can revoke a previously issued access token at any time, after which userinfo and any other authenticated request using that token return 401 Unauthorized.
This endpoint is the partner-initiated counterpart to the user-initiated revocation that fires when an end-user removes a connected app from their CAIRL dashboard. Both paths flip the same is_revoked flag on the access-token row.
Endpoint
POST /api/oauth/revokeContent-Type: application/x-www-form-urlencoded (RFC 7009 §2.1) — JSON also accepted for partner ergonomics.
Request body
| Field | Required | Description |
|---|---|---|
token | Yes | The access token to revoke. |
token_type_hint | No | Per RFC 7009 §2.1; if present, must be access_token. CAIRL does not currently issue refresh tokens. |
client_id | Yes | Your partner API key (the same client_id value used at the /oauth/token endpoint). |
client_secret | Yes | Your partner API key secret. |
Response
Always 200 for any token-related outcome per RFC 7009 §2.2 — a valid token, an unknown token, an expired token, a token belonging to a different partner, and an already-revoked token all produce the same response. This prevents an attacker who can reach this endpoint from enumerating valid jti values.
The response body is empty.
| Status | Meaning |
|---|---|
200 | The request was processed. The token, if it exists and belongs to your partner, is now revoked. |
400 | Malformed request (missing required fields, wrong Content-Type, malformed JSON). |
401 | Invalid client_id or client_secret. |
429 | Rate limit exceeded. The Retry-After header indicates seconds until the next attempt. |
Behavior
- The endpoint authenticates your client using the same hashing path as
/oauth/token. A request with bad credentials returns401immediately and never touches the token DB. - Once authenticated, the JWT signature on the submitted
tokenis verified. If the JWT cannot be verified (bad signature, expired key), the response is still200with no DB write — RFC 7009's no-enumeration property. - If the JWT is valid, the access-token row is looked up by
jti. The token is flipped tois_revoked = trueonly if (a) the row exists, (b) it belongs to the authenticated client's partner (cross-partner revocation attempts silently no-op), and (c) it is not already revoked. - An audit event
oauth.token.revokedis recorded for every successful flip.
Example
curl -X POST https://cairl.app/api/oauth/revoke \
-H "Content-Type: application/x-www-form-urlencoded" \
-d "token=eyJhbGciOiJSUzI1NiI..." \
-d "client_id=cairl_live_..." \
-d "client_secret=cairl_live_..."
HTTP/1.1 200 OK
Cache-Control: no-storeLatency expectation
After a successful revocation, subsequent calls to /api/oauth/userinfo using the same token return 401 within 1 second. Existing in-flight requests are not interrupted.
Errors
| Error code | Status | When |
|---|---|---|
invalid_request | 400 | Missing token / client_id / client_secret, or non-form/JSON body. |
invalid_client | 401 | Unknown client_id, wrong client_secret, or inactive partner key. |
rate_limit_exceeded | 429 | Per-client revocation rate limit hit; check Retry-After. |
See also: Errors.