Exchange a delegated token

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 format `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.

Authentication

X-API-Keystring
Enter your API key for programmatic access

Request

Delegated token exchange params
audiencestringRequiredformat: "uuid"

The target system: the MCP connector id (UUID) to mint a token for.

message_idstringRequiredformat: "uuid"

The message that triggered this agent — the delegation is bound to it.

Response

Delegated token
access_tokenstring
The provider access token that acts as the originator.
oboobject

On-behalf-of: who the token acts as.

token_typeenum

Always bearer.

expires_atstring or nullOptionalformat: "date-time"

Provider-controlled token expiry (UTC ISO 8601). Not platform-bounded, and null when the platform does not know it — treat null as unknown, not as immediate expiry.

Errors

401
Unauthorized Error
403
Forbidden Error
404
Not Found Error
422
Unprocessable Entity Error
429
Too Many Requests Error