How to: handle a revoked token
Two different things can end a token's access, and an integration needs to tell them apart: the token itself dying, and one grant it relied on narrowing or ending while the token keeps working for everything else it can still reach.
A grant narrowing: scope_change
When a grant ends, by revocation, relinquishment, or expiry, the change feed carries it as a scope_change entry with revoked_at set (SYNC-02). A consumer learns
the narrowing from this entry, never from a row silently disappearing off a later read:
{
"seq": 5102,
"op": "scope_change",
"entity": "grant",
"entity_id": "99887766554433221100ffeeddccbbaa",
"person_id": "1a2b3c4d5e6f70819203a4b5c6d7e8f9",
"origin_consumer_id": "01020304050607080910111213141516",
"external_ref": null,
"occurred_at": "2026-08-20T18:00:03.000Z",
"revoked_at": "2026-08-20T18:00:03.000Z"
} The token is fine here. It just no longer reaches this one person; every other grant it holds still works, and further polling stays correct. Nothing to re-authenticate.
The token itself: token_revoked
When an operator revokes a token on the team surface, every call the token makes afterward
answers 401, distinctly from token_invalid, so an
integration can tell "this specific token stopped working" from "this string was never a
valid token":
HTTP/1.1 401 Unauthorized
{
"type": "https://xcathletes.org/docs/errors/token_revoked",
"title": "Token revoked",
"status": 401
} The re-auth path
token_revoked is never recoverable through the API; there is no "renew this
token" call a revoked token can make on itself (renewal only extends an unrevoked token's own
expiry). A human, your team's operator, signs back in at /team/login if their
session has lapsed, mints a fresh token at /team/tokens, and hands it to your
integration out of band.
Treat token_revoked as a stop-and-alert-a-human condition, not something to
retry. scope_change, on the other hand, needs nothing from a human: apply it like
any other feed entry and keep polling.