links
Account links connect a person's separate accounts in different tenants so your app can offer an account switcher.
Account links connect a person's separate accounts in different tenants so your app can offer an account switcher. Identities belong to exactly one tenant, so a consultant working for two clients, or a founder with a personal workspace, has several accounts with their own passwords, factors, and roles. A link records that the same person controls both accounts; it never merges them. See account linking.
What a link does and does not do
A link supplies no permission. Each account keeps its own credentials, MFA, and roles, and holding a session for one account never opens the other: switching always needs a freshly authenticated credential for the target account, so its MFA and sign-in rules still apply. The link is what lets a UI list the other accounts and makes the switch an audited, deliberate step.
Linking is opt-in. It works only when the deployment sets onboarding: { mode: 'linked' }; otherwise
create fails with LINKING_DISABLED. Only ordinary user sessions of two different tenants can link.
Root administrators never can, because root authority must not be reachable from another account.
These methods act on the caller's own accounts, so they need no iam:* permission. Anyone may call them for
themselves.
| Method | What it does | Access |
|---|---|---|
create | Links the caller's account to another account of theirs in a different tenant, proving control of both. | Credential |
list | Lists the accounts linked to the caller's account, for an account-switcher menu. | Credential |
revoke | Removes a link the person no longer wants. | Credential |
switch | Opens a fresh session for a linked account, using a recently authenticated credential for that account. | Credential |
create
Links the caller's account to another account of theirs in a different tenant, proving control of both.
- Permission: The caller's own session, plus a credential for the target account. Both must be recently authenticated user sessions.
- Audited as:
identity:link, in the caller's tenant. - Errors:
LINKING_DISABLEDwhen the deployment does not enable linked onboarding;RECENT_AUTH_REQUIREDwhen either session is not recently authenticated or is a temporary credential;IMPERSONATION_RESTRICTEDfrom a "view as" session;INVALID_LINKwhen either account is a root administrator, either credential is not a user session, or both accounts are in the same tenant;CONFLICTwhen the accounts are already linked.
Recent authentication (by default within the last five minutes) on both sides is the proof: the person has just signed in to each account. Re-linking two accounts whose earlier link was revoked restores that link.
// The person has just signed in to their other account, for example in a second sign-in form.
const link = await iam.api.links.create(credential, {
targetCredential: { token: otherAccountToken },
});Prop
Type
A IdentityLink object:
Prop
Type
Example HTTP request
Only the required fields are shown; replace each <placeholder>. The response is { "data": … } on success or { "error": { "code", "message" } }.
curl -X POST "$IAM_URL/api/iam/links/create" \
-H "Authorization: Bearer $BETTER_IAM_TOKEN" \
-H "Content-Type: application/json" \
-H "X-Better-IAM: 1" \
-d '{
"targetCredential": {}
}'iam.api.links.create(
credential: CredentialInput,
input: { targetCredential: CredentialInput },
): Promise<IdentityLink>list
Lists the accounts linked to the caller's account, for an account-switcher menu.
Used inTyped client,Organizations in the URL
- Permission: The caller's own session.
Each entry has the link id and the other account's identity (identityId, name, email, status) and tenant
(tenantId, tenantName, tenantSlug, tenantStatus). Show the statuses so people understand why a disabled
account or a suspended organization cannot be opened. Revoked links are left out. The call is not audited.
An array of LinkedAccount.
Prop
Type
Example HTTP request
Only the required fields are shown; replace each <placeholder>. The response is { "data": … } on success or { "error": { "code", "message" } }.
curl -X POST "$IAM_URL/api/iam/links/list" \
-H "Authorization: Bearer $BETTER_IAM_TOKEN" \
-H "Content-Type: application/json" \
-H "X-Better-IAM: 1" \
-d '{}'iam.api.links.list(
credential: CredentialInput,
): Promise<LinkedAccount[]>revoke
Removes a link the person no longer wants.
- Permission: The caller's own session, recently authenticated, as either side of the link.
- Audited as:
identity:unlink. - Errors:
NOT_FOUNDwhen the link does not exist or the caller is not one of its two accounts;RECENT_AUTH_REQUIREDwithout recent authentication.
Either account may revoke the link. The accounts themselves are unaffected, and switching between them stops working at once.
Prop
Type
Prop
Type
Example HTTP request
Only the required fields are shown; replace each <placeholder>. The response is { "data": … } on success or { "error": { "code", "message" } }.
curl -X POST "$IAM_URL/api/iam/links/revoke" \
-H "Authorization: Bearer $BETTER_IAM_TOKEN" \
-H "Content-Type: application/json" \
-H "X-Better-IAM: 1" \
-d '{
"linkId": "<linkId>"
}'iam.api.links.revoke(
credential: CredentialInput,
input: { linkId: string },
): Promise<{ revoked: boolean }>switch
Opens a fresh session for a linked account, using a recently authenticated credential for that account.
Used inOrganizations in the URL
- Permission: The caller's own user session, plus a recently authenticated user-session credential for the target account; both accounts must be the two sides of the link.
- Audited as:
identity:switch, in the target account's tenant. - Errors:
RECENT_AUTH_REQUIREDwhen the target credential is not recently authenticated;INVALID_LINKwhen the link is missing or revoked, does not join these two accounts, or either side is a root administrator or not a user session.
The new session carries the target credential's MFA state and authentication time, so the target tenant's rules still apply. Over HTTP the response sets the browser's session cookie, which moves the browser to the target account; the caller's original session is not ended.
const { token, session } = await iam.api.links.switch(credential, {
linkId,
targetCredential: { token: freshTargetToken },
});Prop
Type
A SessionResult object:
Prop
Type
Example HTTP request
Only the required fields are shown; replace each <placeholder>. The response is { "data": … } on success or { "error": { "code", "message" } }.
curl -X POST "$IAM_URL/api/iam/links/switch" \
-H "Authorization: Bearer $BETTER_IAM_TOKEN" \
-H "Content-Type: application/json" \
-H "X-Better-IAM: 1" \
-d '{
"linkId": "<linkId>",
"targetCredential": {}
}'iam.api.links.switch(
credential: CredentialInput,
input: { linkId: string; targetCredential: CredentialInput },
): Promise<SessionResult>Better IAM is created by Sean Filimon
Last updated
invariants
Access invariants are guardrails: statements about who must never, or must always, be able to perform an action on a resource.
oidcProviders
OIDC providers are the external token issuers a tenant trusts for web-identity federation: GitHub Actions, GitLab, a Kubernetes cluster, or a cloud workload id…