Beta

Create a tenant

A new tenant starts with its own users, OAuth settings, audit history and logs. You are its first Tenant Admin.

BTL Admin

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:

ErrorWhat it means in an exchange
invalid_requestThe 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_clientClient authentication failed.
unauthorized_clientThe client authenticated, but is not permitted to use token exchange.
unsupported_grant_typeThe server does not support token exchange at all.
invalid_targetThe server will not issue a token for the requested resource or audience.
invalid_scopeThe 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.

Try it in the Lab

PUT IT INTO PRACTICE

Check your understanding

Try these questions before moving on. If an answer isn't right, use the feedback and try again.

0 of 2 answered correctly

Enable JavaScript to answer these questions and save progress in this browser.

QUESTION 1 OF 2Midway through a long request, an exchange fails with invalid_request because the printer's token has expired. What should the photo API do?

QUESTION 2 OF 2An exchange request times out with no response. Why is one retry reasonable here, when replaying an authorization code is not?

We value your privacy

We use cookies and similar technologies to enhance your browsing experience, and analytics to understand our traffic. By clicking "Allow All", you consent to optional analytics. Cookie Policy

Learn identity