Embed tokens let a partner's backend drop an authenticated ActiFi view (an assessment, dashboard, roadmap, etc.) into an <iframe> on their own site .
The flow is three hops:
(server-to-server) — the partner backend exchanges its API-user credentials for a JWT. This JWT is a bearer credential used for all subsequent server-to-server REST calls.
(server-to-server) — the partner backend calls a REST endpoint with its API-user JWT and gets back a short-lived, single-use token.
(in the browser) — the partner loads a public URL carrying that token as the <iframe src>. ActiFi burns the token, creates a browser session, and redirects in-frame to the destination.
The token that travels through the browser is single-use and expires in
- The
enableEmbedTokens tenant flag is a kill switch; while it is off, both endpoints reject. Ask your ActiFi contact to enable it. - for the mint call. Ask your ActiFi contact to provide you with credentials, then see below to turn those credentials into a JWT.
- that exists (and is active) in the tenant's SSO RelayState table. Raw paths and URLs are accepted — only curated named destinations. Ask your ActiFi contact which RelayState names are available for your tenant.
Before you can mint an embed token you need a JWT for your API user. This is a one-time (or refresh-when-expired) call from your , never the browser.
POST /api/v3/rest/auth/token
Content-Type: application/json
"client_id": "<api-user-username>",
"client_secret": "<api-user-password>"
client_id / client_secret are the username and password of the API-user account your ActiFi contact provisioned for you — there is no separate OAuth client concept here.
"expiresOn": "<ISO date/time string>"
accessToken — the JWT. Use it as Authorization: Bearer <accessToken> on subsequent server-to-server REST calls, including the mint call in Step 2.expiresOn — the JWT is valid for from issuance. Request a new one once it expires (or proactively before each batch of calls); there is no refresh-token flow.
Invalid client_id/client_secret, or the account is inactive. Repeated failures trigger a temporary lockout (10 attempts, then a 5-minute lockout).
a JWT issued to an API-user account can only be used against REST endpoints (paths under /v{n}/rest/..., e.g. the embed-token endpoints below). It cannot be used to access the standard app UI routes.
The API-user JWT must never be exposed to the browser — only mint embed tokens with it from your backend.
Call from your , never the browser — the API-user JWT must not be exposed client-side.
POST /api/v3/rest/auth/embed/token
Authorization: Bearer <API-USER-JWT>
Content-Type: application/json
"type": "tenantSpecificUserId",
"username" or "tenantSpecificUserId".
The identifier for the user the embedded session will act as.
tenantSpecificUserId is convenient when your system already stores its own user ID against the ActiFi user — you don't need to know ActiFi's username.
"embedToken": "Yk9f... (opaque, ~43+ chars)",
embedToken — the single-use token. Use it (see Step 2).expiresIn — token lifetime in seconds (currently 60).
userRef is missing or malformed (bad type, missing value).
Missing / invalid JWT, or the caller is not an API user.
Embed tokens are not enabled for this tenant (kill switch off).
The referenced user cannot be issued a token (unknown, inactive, locked).
422 the message is intentionally generic and does reveal whether the user exists — this prevents user enumeration.
Build the iframe src from the token you just minted plus the destination RelayState, and render it. Loading this URL the exchange — there is no separate call.
GET /pub/auth/embed/exchange?token=<embedToken>&relayState=<relayStateName>
src="https://<your-actifi-host>/pub/auth/embed/exchange?token=Yk9f...&relayState=recent_assessment"
- A RelayState:
relayState=assessment1001 - Some destinations take an , appended with a colon:
relayState=open_roadmap:1234 - Both
token and relayState are and must each appear exactly once.
The endpoint responds with a 302 redirect through ActiFi's auth wall (/auth/wall?jwt=...&redirect=...), which seeds the session and lands the iframe on the destination. Your code does not handle the JWT — it stays inside ActiFi's flow.
The resulting browser session lasts
Every failure renders the ("This link is invalid") so nothing is revealed about the cause. A code query param is included on that page to help ActiFi support correlate the failure in the logs:
Embed tokens are not enabled for this tenant (kill switch off).
Missing/duplicated token or relayState, or an unknown/invalid RelayState.
Token is unknown, expired, already used, or the user is no longer valid.
A 400 (bad/unknown RelayState or malformed params) does consume the token — fix the request and retry with the same token if it is still within its 60-second window. A 410 means the token is spent or expired; mint a new one.
const tokenRes = await fetch("https://<actifi-host>/api/v3/rest/auth/token", {
headers: { "Content-Type": "application/json" },
client_id: API_USER_USERNAME,
client_secret: API_USER_PASSWORD,
const { result: tokenResult } = await tokenRes.json();
const API_USER_JWT = tokenResult.accessToken;
const mintRes = await fetch(
"https://<actifi-host>/api/v3/rest/auth/embed/token",
Authorization: `Bearer ${API_USER_JWT}`,
"Content-Type": "application/json",
userRef: { type: "tenantSpecificUserId", value: partnerUserId },
const { result } = await mintRes.json();
const embedToken = result.embedToken;
const relayState = "recent_assessment";
`https://<actifi-host>/pub/auth/embed/exchange` +
`?token=${encodeURIComponent(embedToken)}` +
`&relayState=${encodeURIComponent(relayState)}`;
<iframe src="{{ src }}" width="100%" height="800" frameborder="0"></iframe>
- It's valid for 1 hour — fetch once and reuse it for all embed-token mints (and other server-to-server REST calls) until it expires, then request a new one.
- A token is single-use and 60-second-lived. Mint one right before you render the iframe; do not cache or reuse embed tokens.
- Both the API-user JWT and the API-user's
client_id/client_secret are privileged credentials and must stay server-side. Only the short-lived embed token ever reaches the client. - The first exchange burns the token. A second exchange of the same token fails raises an internal reuse alert.
- Your site's origin must be permitted to frame the ActiFi destination host. Confirm your domain is on the allow-list with your ActiFi contact.
- Some browser upload features rely on cookies that may be blocked inside a third-party iframe; the session itself does not depend on them, but file-upload-heavy flows may behave differently framed vs. standalone.