> ## Documentation Index
> Fetch the complete documentation index at: https://auth0.com/llms.txt
> Use this file to discover all available pages before exploring further.

> Understand how to link user accounts from various identity providers, so your users can authenticate from any account and be recognized by your app.

# Link User Accounts

export const AuthCodeGroup = ({children, dropdown}) => {
  const [processedChildren, setProcessedChildren] = useState(children);
  useEffect(() => {
    let unsubscribe = null;
    function init() {
      unsubscribe = window.autorun(() => {
        const processChildren = node => {
          if (typeof node === "string") {
            let processedNode = node;
            for (const [key, value] of window.rootStore.variableStore.values.entries()) {
              const escapedKey = key.replaceAll(/[.*+?^${}()|[\]\\]/g, (String.raw)`\$&`);
              processedNode = processedNode.replaceAll(new RegExp(escapedKey, "g"), value);
            }
            return processedNode;
          } else if (Array.isArray(node)) {
            return node.map(processChildren);
          } else if (node && node.props && node.props.children) {
            return {
              ...node,
              props: {
                ...node.props,
                children: processChildren(node.props.children)
              }
            };
          }
          return node;
        };
        setProcessedChildren(processChildren(children));
      });
    }
    if (window.rootStore) {
      init();
    } else {
      window.addEventListener("adu:storeReady", init);
    }
    return () => {
      window.removeEventListener("adu:storeReady", init);
      unsubscribe?.();
    };
  }, [children]);
  return <CodeGroup dropdown={dropdown}>{processedChildren}</CodeGroup>;
};

export const AuthCodeBlock = ({filename, icon, language, highlight, children}) => {
  const [displayText, setDisplayText] = useState(children);
  const [copyText, setCopyText] = useState(children);
  const wrapperRef = React.useRef(null);
  useEffect(() => {
    let unsubscribe = null;
    function init() {
      if (!window.autorun || !window.rootStore) {
        return;
      }
      unsubscribe = window.autorun(() => {
        let processedChildrenForDisplay = children;
        let processedChildrenForCopy = children;
        for (const [key, value] of window.rootStore.variableStore.values.entries()) {
          const escapedKey = key.replaceAll(/[.*+?^${}()|[\]\\]/g, (String.raw)`\$&`);
          let displayValue = value;
          if (key === "{yourClientSecret}" && value !== "{yourClientSecret}") {
            displayValue = value.substring(0, 3) + "*****MASKED*****";
          }
          processedChildrenForDisplay = processedChildrenForDisplay.replaceAll(new RegExp(escapedKey, "g"), displayValue);
          processedChildrenForCopy = processedChildrenForCopy.replaceAll(new RegExp(escapedKey, "g"), value);
        }
        setDisplayText(processedChildrenForDisplay);
        setCopyText(processedChildrenForCopy);
      });
    }
    if (window.rootStore) {
      init();
    } else {
      window.addEventListener("adu:storeReady", init);
    }
    return () => {
      window.removeEventListener("adu:storeReady", init);
      unsubscribe?.();
    };
  }, [children]);
  useEffect(() => {
    if (!wrapperRef.current) return;
    const originalWriteText = navigator.clipboard.writeText.bind(navigator.clipboard);
    let isOverriding = false;
    const handleClick = e => {
      const button = e.target.closest('[data-testid="copy-code-button"]');
      if (!button || !wrapperRef.current.contains(button)) return;
      isOverriding = true;
      navigator.clipboard.writeText = text => {
        if (isOverriding) {
          isOverriding = false;
          navigator.clipboard.writeText = originalWriteText;
          return originalWriteText(copyText);
        }
        return originalWriteText(text);
      };
      setTimeout(() => {
        if (isOverriding) {
          isOverriding = false;
          navigator.clipboard.writeText = originalWriteText;
        }
      }, 100);
    };
    const wrapper = wrapperRef.current;
    wrapper.addEventListener('click', handleClick, true);
    return () => {
      wrapper.removeEventListener('click', handleClick, true);
      if (navigator.clipboard.writeText !== originalWriteText) {
        navigator.clipboard.writeText = originalWriteText;
      }
    };
  }, [copyText]);
  return <div ref={wrapperRef}>
      <CodeBlock filename={filename} icon={icon} language={language} lines highlight={highlight}>
        {displayText}
      </CodeBlock>
    </div>;
};

<Card title="Availability varies by Auth0 plan">
  Both your specific login implementation and your Auth0 plan or custom agreement affect whether this feature is available. To learn more, read [Pricing](https://auth0.com/pricing).
</Card>

You can link user accounts through a variety of methods:

* Action with external linking application
* Auth0 <Tooltip tip="Management API: A product to allow customers to perform administrative tasks." cta="View Glossary" href="/docs/glossary?term=Management+API">Management API</Tooltip>
* Auth0.js library

## Action with external linking application

You can use an Action along with an external linking application to link user accounts with the Management API.

<Warning>
  **Auth0 does not automatically change to the correct primary** **user** after Account Linking, so it must be changed within the Actions code upon successful Account Linking.

  **Every manual account link should prompt the user to enter credentials**. Your tenant should request authentication for both accounts before linking occurs to avoid allowing malicious actors to access legitimate user accounts.
</Warning>

The following steps illustrate an example implementation:

1. Action identifies the potential user accounts to link (if they exist).

2. Action redirects the user to an external linking application with a token payload that contains candidate user identities:

   ```json lines theme={null}
   {
     "current_identity": {
       "user_id": event.user.user_id,
       "provider": event.connection.strategy,
       "connection": event.connection.name
     },
     "candidate_identities": [
       {
         "user_id": USER_ID_1,
         "provider": PROVIDER_1,
         "connection": CONNECTION_1
       },
       {
         "user_id": USER_ID_2,
         "provider": PROVIDER_2,
         "connection": CONNECTION_2
       },
       ...
     ]
   }
   ```

3. External linking application prompts the user to authenticate using the credentials for the account they wish to link.

4. External linking application redirects the user back to the Action with a token payload that contains the primary and secondary user identities:

   ```json lines theme={null}
   {
     "primary_identity": {
       "user_id": PRIMARY_USER_ID,
       "provider": PRIMARY_PROVIDER_STRATEGY,
       "connection": PRIMARY_CONNECTION_NAME,
     },
     "secondary_identity": {
       "user_id": SECONDARY_USER_ID,
       "provider": SECONDARY_PROVIDER_STRATEGY,
       "connection": SECONDARY_CONNECTION_NAME,
     }
   }
   ```

5. Action validates the authenticity and contents of the token.

6. Action calls the Management API to link the accounts based on the results from the external linking application.

7. Action switches to the primary user if it doesn’t match the `event.user.user_id`.

### Example: Account linking Action

```javascript lines expandable theme={null}
const { ManagementClient, AuthenticationClient } = require('auth0');

/**
 * Auth0 Account Linking Action - Production Version
 *
 * Required dependency: auth0@5.3.1
 *
 * This Action detects users with duplicate accounts (same verified email)
 * and redirects them to an external service to manage account linking.
 */

const ACCOUNT_LINKING_TIMESTAMP_KEY = 'account_linking_timestamp';
const TTL_LEEWAY_FACTOR = 0.2;
const PROPERTIES_TO_COMPLETE = ['given_name', 'family_name', 'name'];

/**
 * Get Management API access token with caching
 */
const getManagementAccessToken = async (event, api) => {
  const cacheKey = `mgmt-api-token-${event.secrets.MANAGEMENT_API_CLIENT_ID}`;
  const cached = api.cache.get(cacheKey);

  if (cached && cached.value) {
    return cached.value;
  }

  const auth = new AuthenticationClient({
    domain: event.secrets.MANAGEMENT_API_DOMAIN,
    clientId: event.secrets.MANAGEMENT_API_CLIENT_ID,
    clientSecret: event.secrets.MANAGEMENT_API_CLIENT_SECRET
  });

  const response = await auth.oauth.clientCredentialsGrant({
    audience: `https://${event.secrets.MANAGEMENT_API_DOMAIN}/api/v2/`
  });

  
  const accessToken = response.access_token || response.data?.access_token;
  const expiresIn = response.expires_in || response.data?.expires_in;

  if (accessToken && typeof accessToken === 'string') {
    api.cache.set(cacheKey, accessToken, {
      ttl: expiresIn - expiresIn * TTL_LEEWAY_FACTOR
    });
  }

  return accessToken;
};

/**
 * Get users with the same verified email address
 */
const getUsersWithSameEmail = async (event, api) => {
  const accessToken = await getManagementAccessToken(event, api);
  const management = new ManagementClient({
    domain: event.secrets.MANAGEMENT_API_DOMAIN,
    token: accessToken
  });

  
  const users = await management.users.listUsersByEmail({
    email: event.user.email
  });

  return users;
};

/**
 * Filter and map candidate identities with verified email
 */
const getCandidateIdentitiesWithVerifiedEmail = (event, candidateUsers) => {
  return candidateUsers
    .filter((user) => user.user_id !== event.user.user_id && user.email_verified === true)
    .filter((user) => user.identities && user.identities.length > 0)
    .map((user) => ({
      user_id: user.user_id,
      provider: user.identities[0].provider,
      connection: user.identities[0].connection
    }));
};

/**
 * Link accounts using Management API
 * Links secondary identity TO primary identity
 */
const linkAccounts = async (event, primaryIdentity, secondaryIdentity) => {
  const accessToken = await getManagementAccessToken(event, { cache: { get: () => null, set: () => {} } });

  // Extract the ID part after the | for the API
  const idParts = secondaryIdentity.user_id.split('|');
  const userId = idParts.length > 1 ? idParts[1] : secondaryIdentity.user_id;

  const url = `https://${event.secrets.MANAGEMENT_API_DOMAIN}/api/v2/users/${encodeURIComponent(primaryIdentity.user_id)}/identities`;

  const body = {
    provider: secondaryIdentity.provider,
    user_id: userId
  };

  const response = await fetch(url, {
    method: 'POST',
    headers: {
      'Authorization': `Bearer ${accessToken}`,
      'Content-Type': 'application/json'
    },
    body: JSON.stringify(body)
  });

  if (!response.ok) {
    const errorText = await response.text();
    throw new Error(`Link API error: ${response.status} - ${errorText}`);
  }

  return await response.json();
};

/**
 * Complete missing profile properties from linked identities
 */
const completeProperties = (event, api) => {
  for (const property of PROPERTIES_TO_COMPLETE) {
    if (!event.user[property]) {
      for (const identity of event.user.identities) {
        if (identity.profileData && identity.profileData[property]) {
          api.idToken.setCustomClaim(property, identity.profileData[property]);
          break;
        }
      }
    }
  }
};

/**
 * onExecutePostLogin - Detect duplicate accounts and redirect to linking service
 */
exports.onExecutePostLogin = async (event, api) => {
  // Validate configuration
  if (
    !event.secrets.MANAGEMENT_API_DOMAIN ||
    !event.secrets.MANAGEMENT_API_CLIENT_ID ||
    !event.secrets.MANAGEMENT_API_CLIENT_SECRET ||
    !event.secrets.SESSION_TOKEN_SHARED_SECRET ||
    !event.secrets.ACCOUNT_LINKING_SERVICE_URL
  ) {
    console.log('Missing required configuration - skipping account linking');
    return;
  }

// We won't process users for account linking until they have verified their email address.
  // We might consider rejecting logins here or redirecting users to an external tool to
  // remind the user to confirm their email address before proceeding.
  //
  // In this example, we simply won't process users unless their email is verified.
  if (!event.user.email_verified) {
    return;
  }

  // Skip if already processed
  if (event.user.app_metadata && event.user.app_metadata[ACCOUNT_LINKING_TIMESTAMP_KEY]) {
    completeProperties(event, api);
    return;
  }

  try {
    // Find users with same email
    const candidateUsers = await getUsersWithSameEmail(event, api);

    if (!Array.isArray(candidateUsers) || candidateUsers.length === 0) {
      return;
    }

    // Filter for verified emails
    const candidateIdentities = getCandidateIdentitiesWithVerifiedEmail(event, candidateUsers);

    if (candidateIdentities.length === 0) {
      return;
    }

    // Create session token
    const sessionToken = api.redirect.encodeToken({
      payload: {
        current_identity: {
          user_id: event.user.user_id,
          provider: event.connection.strategy,
          connection: event.connection.name
        },
        candidate_identities: candidateIdentities,
        email: event.user.email,
        continue_url: `https://${event.request.hostname}/continue`
      },
      secret: event.secrets.SESSION_TOKEN_SHARED_SECRET,
      expiresInSeconds: 120
    });

    // Redirect to linking service
    api.redirect.sendUserTo(event.secrets.ACCOUNT_LINKING_SERVICE_URL, {
      query: {
        session_token: sessionToken
      }
    });

  } catch (err) {
    console.error('Account linking error:', err.message);
    // Don't block login on error
  }
};

/**
 * onContinuePostLogin - Process user's linking decision
 */
exports.onContinuePostLogin = async (event, api) => {
  try {
    // Validate response token
    const { primary_identity: primaryIdentity, secondary_identity: secondaryIdentity } = api.redirect.validateToken({
      secret: event.secrets.SESSION_TOKEN_SHARED_SECRET,
      tokenParameterName: 'session_token'
    });

    if (!primaryIdentity || !secondaryIdentity) {
      // User cancelled - continue without linking
      return;
    }

    const currentUserId = event.user.user_id;

    // CRITICAL: Switch to primary user BEFORE linking
    // This prevents "Unable to construct login user" error
    if (primaryIdentity.user_id !== currentUserId) {
      api.authentication.setPrimaryUser(primaryIdentity.user_id);
    }

    // Link the secondary account
    const linkedIdentities = await linkAccounts(event, primaryIdentity, secondaryIdentity);

    if (linkedIdentities && linkedIdentities.length > 0) {
      // Mark as processed
      api.user.setAppMetadata(ACCOUNT_LINKING_TIMESTAMP_KEY, Date.now());
      completeProperties(event, api);
    } else {
      api.access.deny('Account linking failed');
    }
  } catch (err) {
    console.error('onContinuePostLogin error:', err.message);
    api.access.deny('Account linking error: ' + err.message);
  }
};
```

## Management API

You can use the Management API [Link a user account](https://auth0.com/docs/api/v2#!/Users/post_identities) endpoint in two ways:

* User-initiated client-side account linking using <Tooltip tip="Access Token: Authorization credential, in the form of an opaque string or JWT, used to access an API." cta="View Glossary" href="/docs/glossary?term=access+tokens">access tokens</Tooltip> with the `update:current_user_identities` scope.
* Server-side account linking using access tokens with the `update:users` scope.

### User-initiated client-side account linking

For user-initiated client-side account linking, you need an access token that contains the following items in the payload:

* `update:current_user_identites` scope
* `user_id` of the primary account as part of the URL
* <Tooltip tip="ID Token: Credential meant for the client itself, rather than for accessing a resource." cta="View Glossary" href="/docs/glossary?term=ID+token">ID token</Tooltip> of the secondary account that is signed with RS256 and includes an `aud` claim identifying the client that matches the value of the requesting access token's `azp` claim.

An access token that contains the `update:current_user_identities` scope can only be used to update the information of the currently logged-in user. Therefore, this method is suitable for scenarios where the user initiates the linking process.

<AuthCodeGroup>
  ```bash cURL theme={null}
  curl --request POST \
    --url 'https://{yourDomain}/api/v2/users/PRIMARY_ACCOUNT_USER_ID/identities' \
    --header 'authorization: Bearer MANAGEMENT_API_ACCESS_TOKEN' \
    --header 'content-type: application/json' \
    --data '{"link_with":"SECONDARY_ACCOUNT_ID_TOKEN"}'
  ```

  ```csharp C# theme={null}
  var client = new RestClient("https://{yourDomain}/api/v2/users/PRIMARY_ACCOUNT_USER_ID/identities");
  var request = new RestRequest(Method.POST);
  request.AddHeader("authorization", "Bearer MANAGEMENT_API_ACCESS_TOKEN");
  request.AddHeader("content-type", "application/json");
  request.AddParameter("application/json", "{"link_with":"SECONDARY_ACCOUNT_ID_TOKEN"}", ParameterType.RequestBody);
  IRestResponse response = client.Execute(request);
  ```

  ```go Go theme={null}
  package main

  import (
  	"fmt"
  	"strings"
  	"net/http"
  	"io/ioutil"
  )

  func main() {

  	url := "https://{yourDomain}/api/v2/users/PRIMARY_ACCOUNT_USER_ID/identities"

  	payload := strings.NewReader("{"link_with":"SECONDARY_ACCOUNT_ID_TOKEN"}")

  	req, _ := http.NewRequest("POST", url, payload)

  	req.Header.Add("authorization", "Bearer MANAGEMENT_API_ACCESS_TOKEN")
  	req.Header.Add("content-type", "application/json")

  	res, _ := http.DefaultClient.Do(req)

  	defer res.Body.Close()
  	body, _ := ioutil.ReadAll(res.Body)

  	fmt.Println(res)
  	fmt.Println(string(body))

  }
  ```

  ```java Java theme={null}
  HttpResponse<String> response = Unirest.post("https://{yourDomain}/api/v2/users/PRIMARY_ACCOUNT_USER_ID/identities")
    .header("authorization", "Bearer MANAGEMENT_API_ACCESS_TOKEN")
    .header("content-type", "application/json")
    .body("{"link_with":"SECONDARY_ACCOUNT_ID_TOKEN"}")
    .asString();
  ```

  ```javascript Node.JS theme={null}
  var axios = require("axios").default;

  var options = {
    method: 'POST',
    url: 'https://{yourDomain}/api/v2/users/PRIMARY_ACCOUNT_USER_ID/identities',
    headers: {
      authorization: 'Bearer MANAGEMENT_API_ACCESS_TOKEN',
      'content-type': 'application/json'
    },
    data: {link_with: 'SECONDARY_ACCOUNT_ID_TOKEN'}
  };

  axios.request(options).then(function (response) {
    console.log(response.data);
  }).catch(function (error) {
    console.error(error);
  });
  ```

  ```php PHP theme={null}
  $curl = curl_init();

  curl_setopt_array($curl, [
    CURLOPT_URL => "https://{yourDomain}/api/v2/users/PRIMARY_ACCOUNT_USER_ID/identities",
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_ENCODING => "",
    CURLOPT_MAXREDIRS => 10,
    CURLOPT_TIMEOUT => 30,
    CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
    CURLOPT_CUSTOMREQUEST => "POST",
    CURLOPT_POSTFIELDS => "{"link_with":"SECONDARY_ACCOUNT_ID_TOKEN"}",
    CURLOPT_HTTPHEADER => [
      "authorization: Bearer MANAGEMENT_API_ACCESS_TOKEN",
      "content-type: application/json"
    ],
  ]);

  $response = curl_exec($curl);
  $err = curl_error($curl);

  curl_close($curl);

  if ($err) {
    echo "cURL Error #:" . $err;
  } else {
    echo $response;
  }
  ```

  ```python Python theme={null}
  import http.client

  conn = http.client.HTTPSConnection("")

  payload = "{"link_with":"SECONDARY_ACCOUNT_ID_TOKEN"}"

  headers = {
      'authorization': "Bearer MANAGEMENT_API_ACCESS_TOKEN",
      'content-type': "application/json"
      }

  conn.request("POST", "/{yourDomain}/api/v2/users/PRIMARY_ACCOUNT_USER_ID/identities", payload, headers)

  res = conn.getresponse()
  data = res.read()

  print(data.decode("utf-8"))
  ```

  ```ruby Ruby theme={null}
  require 'uri'
  require 'net/http'
  require 'openssl'

  url = URI("https://{yourDomain}/api/v2/users/PRIMARY_ACCOUNT_USER_ID/identities")

  http = Net::HTTP.new(url.host, url.port)
  http.use_ssl = true
  http.verify_mode = OpenSSL::SSL::VERIFY_NONE

  request = Net::HTTP::Post.new(url)
  request["authorization"] = 'Bearer MANAGEMENT_API_ACCESS_TOKEN'
  request["content-type"] = 'application/json'
  request.body = "{"link_with":"SECONDARY_ACCOUNT_ID_TOKEN"}"

  response = http.request(request)
  puts response.read_body
  ```
</AuthCodeGroup>

### Server-side account linking

For server-side account linking, you need an access token that contains the following items in the payload:

* `update:users` scope
* `user_id` of the primary account as part of the URL
* `user_id` of the secondary account
* ID token of the secondary account that is signed with RS256 and includes an `aud` claim identifying the client that matches the value of the requesting access token's `azp` claim.

Access tokens that contain the `update:users` scope can be used to update the information of any user. Therefore, this method is intended for use in server-side code only.

<AuthCodeGroup>
  ```bash cURL theme={null}
  curl --request POST \
    --url 'https://{yourDomain}/api/v2/users/PRIMARY_ACCOUNT_USER_ID/identities' \
    --header 'authorization: Bearer MANAGEMENT_API_ACCESS_TOKEN' \
    --header 'content-type: application/json' \
    --data '{"provider":"SECONDARY_ACCOUNT_PROVIDER", "user_id": "SECONDARY_ACCOUNT_USER_ID"}'
  ```

  ```csharp C# theme={null}
  var client = new RestClient("https://{yourDomain}/api/v2/users/PRIMARY_ACCOUNT_USER_ID/identities");
  var request = new RestRequest(Method.POST);
  request.AddHeader("authorization", "Bearer MANAGEMENT_API_ACCESS_TOKEN");
  request.AddHeader("content-type", "application/json");
  request.AddParameter("application/json", "{"provider":"SECONDARY_ACCOUNT_PROVIDER", "user_id": "SECONDARY_ACCOUNT_USER_ID"}", ParameterType.RequestBody);
  IRestResponse response = client.Execute(request);
  ```

  ```go Go theme={null}
  package main

  import (
  	"fmt"
  	"strings"
  	"net/http"
  	"io/ioutil"
  )

  func main() {

  	url := "https://{yourDomain}/api/v2/users/PRIMARY_ACCOUNT_USER_ID/identities"

  	payload := strings.NewReader("{"provider":"SECONDARY_ACCOUNT_PROVIDER", "user_id": "SECONDARY_ACCOUNT_USER_ID"}")

  	req, _ := http.NewRequest("POST", url, payload)

  	req.Header.Add("authorization", "Bearer MANAGEMENT_API_ACCESS_TOKEN")
  	req.Header.Add("content-type", "application/json")

  	res, _ := http.DefaultClient.Do(req)

  	defer res.Body.Close()
  	body, _ := ioutil.ReadAll(res.Body)

  	fmt.Println(res)
  	fmt.Println(string(body))

  }
  ```

  ```java Java theme={null}
  HttpResponse<String> response = Unirest.post("https://{yourDomain}/api/v2/users/PRIMARY_ACCOUNT_USER_ID/identities")
    .header("authorization", "Bearer MANAGEMENT_API_ACCESS_TOKEN")
    .header("content-type", "application/json")
    .body("{"provider":"SECONDARY_ACCOUNT_PROVIDER", "user_id": "SECONDARY_ACCOUNT_USER_ID"}")
    .asString();
  ```

  ```javascript Node.JS theme={null}
  var axios = require("axios").default;

  var options = {
    method: 'POST',
    url: 'https://{yourDomain}/api/v2/users/PRIMARY_ACCOUNT_USER_ID/identities',
    headers: {
      authorization: 'Bearer MANAGEMENT_API_ACCESS_TOKEN',
      'content-type': 'application/json'
    },
    data: {provider: 'SECONDARY_ACCOUNT_PROVIDER', user_id: 'SECONDARY_ACCOUNT_USER_ID'}
  };

  axios.request(options).then(function (response) {
    console.log(response.data);
  }).catch(function (error) {
    console.error(error);
  });
  ```

  ```php PHP theme={null}
  $curl = curl_init();

  curl_setopt_array($curl, [
    CURLOPT_URL => "https://{yourDomain}/api/v2/users/PRIMARY_ACCOUNT_USER_ID/identities",
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_ENCODING => "",
    CURLOPT_MAXREDIRS => 10,
    CURLOPT_TIMEOUT => 30,
    CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
    CURLOPT_CUSTOMREQUEST => "POST",
    CURLOPT_POSTFIELDS => "{"provider":"SECONDARY_ACCOUNT_PROVIDER", "user_id": "SECONDARY_ACCOUNT_USER_ID"}",
    CURLOPT_HTTPHEADER => [
      "authorization: Bearer MANAGEMENT_API_ACCESS_TOKEN",
      "content-type: application/json"
    ],
  ]);

  $response = curl_exec($curl);
  $err = curl_error($curl);

  curl_close($curl);

  if ($err) {
    echo "cURL Error #:" . $err;
  } else {
    echo $response;
  }
  ```

  ```python Python theme={null}
  import http.client

  conn = http.client.HTTPSConnection("")

  payload = "{"provider":"SECONDARY_ACCOUNT_PROVIDER", "user_id": "SECONDARY_ACCOUNT_USER_ID"}"

  headers = {
      'authorization': "Bearer MANAGEMENT_API_ACCESS_TOKEN",
      'content-type': "application/json"
      }

  conn.request("POST", "/{yourDomain}/api/v2/users/PRIMARY_ACCOUNT_USER_ID/identities", payload, headers)

  res = conn.getresponse()
  data = res.read()

  print(data.decode("utf-8"))
  ```

  ```ruby Ruby theme={null}
  require 'uri'
  require 'net/http'
  require 'openssl'

  url = URI("https://{yourDomain}/api/v2/users/PRIMARY_ACCOUNT_USER_ID/identities")

  http = Net::HTTP.new(url.host, url.port)
  http.use_ssl = true
  http.verify_mode = OpenSSL::SSL::VERIFY_NONE

  request = Net::HTTP::Post.new(url)
  request["authorization"] = 'Bearer MANAGEMENT_API_ACCESS_TOKEN'
  request["content-type"] = 'application/json'
  request.body = "{"provider":"SECONDARY_ACCOUNT_PROVIDER", "user_id": "SECONDARY_ACCOUNT_USER_ID"}"

  response = http.request(request)
  puts response.read_body
  ```
</AuthCodeGroup>

The secondary user account's `user_id` and `provider` can be deduced by its unique identifier. For example, for the identifier `google-oauth2|108091299999329986433`:

* `provider` is `google-oauth2`
* `user_id` is `108091299999329986433`

Alternatively, you can you can send the secondary account's ID token instead of the `provider` and `user_id`:

<AuthCodeGroup>
  ```bash cURL theme={null}
  curl --request POST \
    --url 'https://{yourDomain}/api/v2/users/PRIMARY_ACCOUNT_USER_ID/identities' \
    --header 'authorization: Bearer MANAGEMENT_API_ACCESS_TOKEN' \
    --header 'content-type: application/json' \
    --data '{"link_with":"SECONDARY_ACCOUNT_ID_TOKEN"}'
  ```

  ```csharp C# theme={null}
  var client = new RestClient("https://{yourDomain}/api/v2/users/PRIMARY_ACCOUNT_USER_ID/identities");
  var request = new RestRequest(Method.POST);
  request.AddHeader("authorization", "Bearer MANAGEMENT_API_ACCESS_TOKEN");
  request.AddHeader("content-type", "application/json");
  request.AddParameter("application/json", "{"link_with":"SECONDARY_ACCOUNT_ID_TOKEN"}", ParameterType.RequestBody);
  IRestResponse response = client.Execute(request);
  ```

  ```go Go theme={null}
  package main

  import (
  	"fmt"
  	"strings"
  	"net/http"
  	"io/ioutil"
  )

  func main() {

  	url := "https://{yourDomain}/api/v2/users/PRIMARY_ACCOUNT_USER_ID/identities"

  	payload := strings.NewReader("{"link_with":"SECONDARY_ACCOUNT_ID_TOKEN"}")

  	req, _ := http.NewRequest("POST", url, payload)

  	req.Header.Add("authorization", "Bearer MANAGEMENT_API_ACCESS_TOKEN")
  	req.Header.Add("content-type", "application/json")

  	res, _ := http.DefaultClient.Do(req)

  	defer res.Body.Close()
  	body, _ := ioutil.ReadAll(res.Body)

  	fmt.Println(res)
  	fmt.Println(string(body))

  }
  ```

  ```java Java theme={null}
  HttpResponse<String> response = Unirest.post("https://{yourDomain}/api/v2/users/PRIMARY_ACCOUNT_USER_ID/identities")
    .header("authorization", "Bearer MANAGEMENT_API_ACCESS_TOKEN")
    .header("content-type", "application/json")
    .body("{"link_with":"SECONDARY_ACCOUNT_ID_TOKEN"}")
    .asString();
  ```

  ```javascript Node.JS theme={null}
  var axios = require("axios").default;

  var options = {
    method: 'POST',
    url: 'https://{yourDomain}/api/v2/users/PRIMARY_ACCOUNT_USER_ID/identities',
    headers: {
      authorization: 'Bearer MANAGEMENT_API_ACCESS_TOKEN',
      'content-type': 'application/json'
    },
    data: {link_with: 'SECONDARY_ACCOUNT_ID_TOKEN'}
  };

  axios.request(options).then(function (response) {
    console.log(response.data);
  }).catch(function (error) {
    console.error(error);
  });
  ```

  ```php PHP theme={null}
  $curl = curl_init();

  curl_setopt_array($curl, [
    CURLOPT_URL => "https://{yourDomain}/api/v2/users/PRIMARY_ACCOUNT_USER_ID/identities",
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_ENCODING => "",
    CURLOPT_MAXREDIRS => 10,
    CURLOPT_TIMEOUT => 30,
    CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
    CURLOPT_CUSTOMREQUEST => "POST",
    CURLOPT_POSTFIELDS => "{"link_with":"SECONDARY_ACCOUNT_ID_TOKEN"}",
    CURLOPT_HTTPHEADER => [
      "authorization: Bearer MANAGEMENT_API_ACCESS_TOKEN",
      "content-type: application/json"
    ],
  ]);

  $response = curl_exec($curl);
  $err = curl_error($curl);

  curl_close($curl);

  if ($err) {
    echo "cURL Error #:" . $err;
  } else {
    echo $response;
  }
  ```

  ```python Python theme={null}
  import http.client

  conn = http.client.HTTPSConnection("")

  payload = "{"link_with":"SECONDARY_ACCOUNT_ID_TOKEN"}"

  headers = {
      'authorization': "Bearer MANAGEMENT_API_ACCESS_TOKEN",
      'content-type': "application/json"
      }

  conn.request("POST", "/{yourDomain}/api/v2/users/PRIMARY_ACCOUNT_USER_ID/identities", payload, headers)

  res = conn.getresponse()
  data = res.read()

  print(data.decode("utf-8"))
  ```

  ```ruby Ruby theme={null}
  require 'uri'
  require 'net/http'
  require 'openssl'

  url = URI("https://{yourDomain}/api/v2/users/PRIMARY_ACCOUNT_USER_ID/identities")

  http = Net::HTTP.new(url.host, url.port)
  http.use_ssl = true
  http.verify_mode = OpenSSL::SSL::VERIFY_NONE

  request = Net::HTTP::Post.new(url)
  request["authorization"] = 'Bearer MANAGEMENT_API_ACCESS_TOKEN'
  request["content-type"] = 'application/json'
  request.body = "{"link_with":"SECONDARY_ACCOUNT_ID_TOKEN"}"

  response = http.request(request)
  puts response.read_body
  ```
</AuthCodeGroup>

## Auth0.js library

You can use the Auth0.js library to perform client-side account linking. Read [Auth0.js Reference > User management](/docs/libraries/auth0js#user-management) to learn more.

## Learn more

* [User Account Linking: Server-Side Implementation](/docs/manage-users/user-accounts/user-account-linking/suggested-account-linking-server-side-implementation)
* [User-Initiated Account Linking: Client-Side Implementation](/docs/manage-users/user-accounts/user-account-linking/user-initiated-account-linking-client-side-implementation)
* [Unlink User Accounts](/docs/manage-users/user-accounts/user-account-linking/unlink-user-accounts)
