BetterIAM
Server API

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.

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.

Methods4
Serveriam.api.links
Clientclient.links
HTTPPOST /api/iam/links/*
MethodWhat it doesAccess
createLinks the caller's account to another account of theirs in a different tenant, proving control of both.Credential
listLists the accounts linked to the caller's account, for an account-switcher menu.Credential
revokeRemoves a link the person no longer wants.Credential
switchOpens 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.

POST/api/iam/links/create
client.links.create()Credential
  • 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_DISABLED when the deployment does not enable linked onboarding; RECENT_AUTH_REQUIRED when either session is not recently authenticated or is a temporary credential; IMPERSONATION_RESTRICTED from a "view as" session; INVALID_LINK when either account is a root administrator, either credential is not a user session, or both accounts are in the same tenant; CONFLICT when 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 },
});
Input

Prop

Type

Returns

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": {}
}'
Signature
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.

POST/api/iam/links/list
client.links.list()Credential

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.

Returns

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 '{}'
Signature
iam.api.links.list(
  credential: CredentialInput,
): Promise<LinkedAccount[]>

revoke

Removes a link the person no longer wants.

POST/api/iam/links/revoke
client.links.revoke()Credential
  • Permission: The caller's own session, recently authenticated, as either side of the link.
  • Audited as: identity:unlink.
  • Errors: NOT_FOUND when the link does not exist or the caller is not one of its two accounts; RECENT_AUTH_REQUIRED without recent authentication.

Either account may revoke the link. The accounts themselves are unaffected, and switching between them stops working at once.

Input

Prop

Type

Returns

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>"
}'
Signature
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.

POST/api/iam/links/switch
client.links.switch()Credential

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_REQUIRED when the target credential is not recently authenticated; INVALID_LINK when 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 },
});
Input

Prop

Type

Returns

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": {}
}'
Signature
iam.api.links.switch(
  credential: CredentialInput,
  input: { linkId: string; targetCredential: CredentialInput },
): Promise<SessionResult>

Was this page helpful?

Better IAM is created by Sean Filimon

Last updated

On this page