Redeems a delegated token bound to a single chat message.
Given the message that triggered the agent (message_id) and the target
system (audience), the platform verifies — all fail-closed — that:
- the calling agent is within its per-agent exchange rate limit; this is
checked before anything else, so a limited caller gets
rate_limited (429)
even for a message id that does not exist;
- the message exists and was delivered to the calling agent, and the
exchange happens within the org’s processing window (the exchange is not a
standing 24h capability);
- the calling agent was @mentioned on the message;
- the message crossed an ownership boundary (a delegation exists) and the
originator is not the agent’s own owner;
- the originator is still an active participant of the room;
- the per-message exchange cap has not been exhausted;
- the originator has consented to this agent for this system, that system
is on the consent’s allowlist (the audience ceiling), the connector permits
delegation, and both parties are in the same org.
On success it returns a token that reads the originator’s data for the one
system named by audience, plus an obo (“on behalf of”) block naming who
the token acts as.
audience is the MCP connector id (a UUID) of the target system — the same
identifier that appears on the originator’s consent allowlist. It is not the
connector’s display name.
Processing window
The window opens when the human asked, not when you received the message:
it is measured from the server-minted metadata.delegation.minted_at on the
triggering message, which is set at message creation and stripped from every
client write.
Start your clock there, not on receipt. Any queueing, retry or restart between
the ask and your delivery has already consumed part of the window, so a
deadline computed from receipt-time will be later than the platform’s and the
exchange will answer window_expired while your own timer still looks alive.
A window_expired answer costs you nothing: the window is checked before the
per-message exchange counter, so a late request does not consume part of the
message’s budget. Two things consume it: an exchange that returns a token, and
a provider_not_connected caused by a stored credential the platform tried to
refresh and could not — that attempt is a real round trip to the provider, and
retrying it cannot succeed. The first-run form of provider_not_connected
(the originator has connected nothing yet) is free, so the retry that error
asks you to make once they connect is free too.
Token lifetime
For the OAuth/MCP connector kind the returned access_token is the
provider’s token; its expires_at is provider-controlled (typically
hours) and is not platform-bounded — which is exactly why the exchange is
bound to one message’s processing window rather than granted a long standing
one. When the stored connection carries no provider expiry the platform does
not know it, and expires_at is null — that means unknown, not expired.