---
title: "Exchange For Impersonation Token"
method: POST
path: "/api/platform/impersonation/exchange"
tags: ["platform"]
---

# Exchange For Impersonation Token

`POST /api/platform/impersonation/exchange`

Exchange the caller's own SPA access token for a short-lived impersonation token.

The FGA gate lives on the router include (see
:mod:`app.authorization.matrix`): this POST requires ``editor`` on the
dock impersonation module instance, so impersonation is delegable per
persona rather than tied to blanket app-dock ownership. Two refusals
layer on top:

* **401 No token to exchange** — only end users can drive this flow,
  so service-account principals are rejected outright. The SPA token
  is reused as the ``subject_token`` for RFC 8693, so an
  impersonating session cannot itself act as the subject of another
  exchange (the dock identity layer rejects ``act`` on a token that
  already has ``act`` set).

## Request body

- ImpersonationExchangeRequest — Body for ``POST /api/platform/impersonation/exchange``. Accepts ``targetUserId`` (preferred) or ``targetEmail`` (fallback). The Users table always has the Auth0 ``user_id`` available on each row, so the SPA's primary path passes it through verbatim — that keeps the lookup unambiguous (``GET /users/{user_id}``) and avoids the same-email-across-connections 409 that the by-email lookup raises when, for example, an admin has both ``Username-Password-Authentication`` and ``google-oauth2`` accounts on the same address. ``targetEmail`` is preserved for callers that don't have the user_id (a future search-by-email surface, scripted callers, etc.) and for display purposes alongside the dialog.
  - `targetUserId` string, nullable — Auth0 ``user_id`` of the user to impersonate. Preferred over ``targetEmail`` because it disambiguates the same-email-across-connections case. Always available from the Users page row.
  - `targetEmail` string, nullable — Email of the user to impersonate. Used when ``targetUserId`` is not provided; the dock backend resolves it via ``GET /users-by-email`` and refuses with 409 if the email matches multiple Auth0 users.
  - `targetName` string, nullable — Optional display name passed through for the SPA banner.
  - `mode` 'full' | 'readonly' — ``full`` permits writes; ``readonly`` blocks every non-safe HTTP method server-side.

## Response `200`

Successful Response

- ImpersonationExchangeResponse — Body returned by ``POST /api/platform/impersonation/exchange``. The SPA hands this to ``setImpersonationSession`` from ``@crisp/ui-toolkit/auth``, which writes it into ``window.CrispRuntime.impersonation`` so every React root on the page (host + federated remotes) starts using the impersonation token on its next ``getAccessTokenSilently()`` call. ``expires_at`` / ``issued_at`` are seconds-since-epoch from the dock backend's wall clock so the SPA can compute ``expiresAt: expiresAt * 1000`` without trusting its own clock.
  - `accessToken` string, required
  - `tokenType` string
  - `expiresIn` integer, required
  - `expiresAt` integer, required
  - `issuedAt` integer, required
  - `targetUserId` string, required
  - `targetEmail` string, required
  - `targetName` string, nullable
  - `actorEmail` string, required
  - `mode` 'full' | 'readonly', required

## Other responses

- `422` — Validation Error

---

[API](https://skmtc.dev/gocrisp/apis/crisp-app-dock.md) · [All operations](https://skmtc.dev/gocrisp/apis/crisp-app-dock/llms.txt) · [OpenAPI document](https://skmtc-service-production.skmtc.workers.dev/v1/apis/gocrisp/crisp-app-dock/revisions/e56bd3c1a9db/schema)
