Errors and failure cases
Most exchanges finish in a few milliseconds and nobody notices them. When one fails, the failure lands in the middle of someone else's request. The printer is waiting for album 42, and the photo API has to decide what to tell it.
A useful answer depends on knowing which part of the exchange failed, and on not making matters worse by retrying.
Reading the error
An exchange fails like any other token request, with a JSON error from the token endpoint, usually with status 400. A client that tried to authenticate with HTTP Basic and failed receives 401 instead. All identifiers in these examples are fictional.
HTTP/1.1 400 Bad Request
Content-Type: application/json
Cache-Control: no-store
Pragma: no-cache
{
"error": "invalid_request"
}
Token exchange reuses the token endpoint's error codes and adds one from Resource indicators:
| Error | What it means in an exchange |
|---|---|
invalid_request | The request is malformed, for example missing subject_token_type, or sending actor_token_type without an actor token. Also used when the subject or actor token is invalid or unacceptable: expired, revoked, issued for a different API, or from an issuer the server does not trust. |
invalid_client | Client authentication failed. |
unauthorized_client | The client authenticated, but is not permitted to use token exchange. |
unsupported_grant_type | The server does not support token exchange at all. |
invalid_target | The server will not issue a token for the requested resource or audience. |
invalid_scope | The requested scope is unknown, or more than the policy and the subject token allow. |
The first row covers a lot. The specification requires invalid_request whenever the subject or actor token is invalid for any reason or unacceptable by policy. If you know the other grants, you might have expected invalid_grant, the code for a bad authorization code or refresh token, and some servers do answer a rejected subject token that way. Either code tells the client that the exchange it sent will not succeed as it stands. The response does not say which check failed, and an optional error_description is written for developers, not for program logic.
Tokens that expire mid-operation
The printer's token lasts ten minutes, until 09:10. Suppose that at 09:09:30 the printer asks for a large album of 240 photos. The photo API exchanges the token at once and receives a storage token that expires at 09:10:00, capped by the printer's token. At 09:10:05, with 60 files still to fetch, the storage token has expired, and a fresh exchange fails with invalid_request because the subject token has expired too.
The photo API cannot repair this, and should not work around it. Each shortcut recreates a problem that token exchange exists to avoid. Finishing the work with its own client credentials token would read your files with authority you never gave. Forwarding the printer's token to storage would be refused, as it should be.
The honest answer is an error the printer can act on. The photo API responds with status 401 and error="invalid_token" in its WWW-Authenticate header, as Using access tokens described, and the printer obtains a fresh access token and asks again. A client that replaces tokens well ahead of expiry rarely meets this, but every client should handle it.
The photo API can make it rarer too. Before starting work that will plainly outlast the subject token, it can check how long that token has left and ask for a fresh one at the start, rather than failing halfway through. And where work genuinely continues after the caller has gone, such as preparing print-resolution files overnight, design that authorization explicitly instead of stretching a request's token. The specification allows a refresh token in an exchange response for cases where the client needs access after the original credential has stopped being valid, and expects deployments to document when one is issued.
Retrying with care
Some failures are worth retrying. A timeout, a dropped connection, or a 5xx status from the token endpoint can be temporary, and one or two retries with a growing delay are reasonable. Unlike an authorization code, the subject token is not used up by an exchange, as Following a token exchange showed, so a repeated exchange simply issues another short-lived token.
The error codes in the table are different. Each one reports a decision, and repeating the same request gets the same decision. A loop between the photo API and the token endpoint only adds load. A loop that tries other targets or scopes until something succeeds is worse, because it probes the server's policy.
Errors from the downstream service follow the same reasoning. If the storage service answers 401 with invalid_token because the storage token expired, the photo API can exchange again, once, if the subject token is still valid, and retry the call once. A 403 with insufficient_scope means the token lacks the access the call needs, and exchanging the same way again will not change the policy.
When an exchange fails, the downstream call does not happen. The photo API does not fall back to forwarding the original token, to its own client credentials, or to a cached token from another request. It fails closed and says so. What it tells the printer depends on whose problem it is. An expired printer token deserves a 401 the printer can fix. unauthorized_client or invalid_target means the photo service's own configuration is wrong, so the photo API answers with a server error and alerts its operators, rather than a 401 that would send the printer to replace a perfectly good token.
Logging the chain
When something fails several services deep, the first question is who was acting for whom. Each party can record its part without recording a single token:
Authorization server, 2026-10-01 09:02:00 UTC
event: token exchange
client: photo-api
subject: user-2048
subject token jti: demo-token-id-7
issued token jti: demo-token-id-storage-1
target: https://storage.photos.example
scope: storage.read
outcome: issued
Storage service, 2026-10-01 09:02:00 UTC
event: object read
request ID: demo-request-31
subject: user-2048
current actor: photo-api
token jti: demo-token-id-storage-1
object: photo-1187
outcome: allowed
The jti values link the records. A token used at the storage service leads back to the exchange that produced it, and from there to the printer's token behind it. A request ID passed along with each internal call lets the services' records be joined even when no token was issued, such as when an exchange fails. The photo API's own log ties the printer's request to both.
What stays out matters as much. Never log the tokens, the Authorization header, or the body of an exchange request, because each contains a credential someone could use. A refused exchange is recorded the same way as a successful one, with its error code, client, subject, and target, which is often all an operator needs to see that a policy or configuration has changed.