# links (/docs/reference/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](/docs/guides/concepts/tenants-and-identities#account-linking).

## What a link does and does not do [#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`](#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`](#create) | Links the caller's account to another account of theirs in a different tenant, proving control of both. | Credential |
| [`list`](#list)     | Lists the accounts linked to the caller's account, for an account-switcher menu.                        | Credential |
| [`revoke`](#revoke) | Removes a link the person no longer wants.                                                              | Credential |
| [`switch`](#switch) | Opens a fresh session for a linked account, using a recently authenticated credential for that account. | Credential |

## create [#create]

Links the caller's account to another account of theirs in a different tenant, proving control of both.

**HTTP:** `POST /api/iam/links/create` (requires a credential) · **Browser client:** `client.links.create()`

* **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.

```ts
// 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 },
});
```

```ts title="Signature"
iam.api.links.create(
  credential: CredentialInput,
  input: { targetCredential: CredentialInput },
): Promise<IdentityLink>
```

## list [#list]

Lists the accounts linked to the caller's account, for an account-switcher menu.

**HTTP:** `POST /api/iam/links/list` (requires a credential) · **Browser client:** `client.links.list()`

* **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.

```ts title="Signature"
iam.api.links.list(
  credential: CredentialInput,
): Promise<LinkedAccount[]>
```

## revoke [#revoke]

Removes a link the person no longer wants.

**HTTP:** `POST /api/iam/links/revoke` (requires a credential) · **Browser client:** `client.links.revoke()`

* **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.

```ts title="Signature"
iam.api.links.revoke(
  credential: CredentialInput,
  input: { linkId: string },
): Promise<{ revoked: boolean }>
```

## switch [#switch]

Opens a fresh session for a linked account, using a recently authenticated credential for that account.

**HTTP:** `POST /api/iam/links/switch` (requires a credential) · **Browser client:** `client.links.switch()`

* **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.

```ts
const { token, session } = await iam.api.links.switch(credential, {
  linkId,
  targetCredential: { token: freshTargetToken },
});
```

```ts title="Signature"
iam.api.links.switch(
  credential: CredentialInput,
  input: { linkId: string; targetCredential: CredentialInput },
): Promise<SessionResult>
```
