> For clean Markdown of any page, append .md to the page URL. > For a complete documentation index, see https://docs.merge.dev/merge-unified/merge-link/llms.txt. > For AI client integration (Claude Code, Cursor, etc.), connect to the MCP server at https://docs.merge.dev/_mcp/server. # Merge Link > How to embed Merge Link (React, Vue, or vanilla JS). Covers link token creation, the authorization flow, and exchanging a public token for an account token. Merge Link is a drop-in UI component for React, Vue, or vanilla JS that your end user opens to pick an integration, sign in with their provider, and authorize data access. When they finish, you get an `account_token` you store in your database and use for every Merge API call on their behalf. This guide walks through all three sides of the token handshake: a backend endpoint that mints a short-lived `link_token`, the frontend component that consumes that token and returns a `public_token` on success, and a second backend endpoint that swaps the `public_token` for a permanent `account_token`. Code for five backend languages and three frontend frameworks follows each step. > **Note** > > **Other options:** This guide covers embedded Merge Link, which we recommend for most integrations. Merge also supports [Magic Link](/merge-unified/authentication/magic-link) — send end users a URL to authorize integrations without any frontend code. You can also use the [Single Integration](/merge-unified/authentication/single-integration) parameter to limit Merge Link to a specific provider instead of showing the full integration picker. --- ## Add Merge Link to your product Merge Link utilizes a series of token exchanges to securely authenticate your users' integrations. In this guide, you'll set up the following in your application: 1. Get a `link_token` to initialize a Merge Link session for your end user. 2. Make Merge Link appear in your frontend. 3. Swap for an `account_token`, which authenticates future requests to the Unified API. --- #### Backend – Get link token In your backend, set up a POST request to initialize a Merge Link session and get a `link_token` from this URL: ``` https://api.merge.dev/api/integrations/create-link-token ``` **Note:** If you are using our SDKs, you can get a Link Token using a category-specific endpoint. For example, for HRIS, it can be found at [https://docs.merge.dev/hris/link-token/](https://docs.merge.dev/hris/link-token/). Pass in your [Production Access API key](https://app.merge.dev/keys) as a header; use a production access key to create a production Linked Account or a test access key to create a test Linked Account. ### POST request body Configure your Merge Link with the following parameters: | Parameter | Type | Description | | ----------------------------------------------- | ------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | `end_user_origin_id` | String | Unique ID for your end user. For more information see [End user origin ID](https://help.merge.dev/en/articles/8032943-end-user-origin-id-best-practices) guide. | | `end_user_organization_name` | String | Your end user's organization. | | `end_user_email_address` | String | Your end user's email address. | | `categories` | Array | The integration categories to show in Merge Link. `["hris", "ats", "accounting", "ticketing", "crm", "filestorage", "knowledgebase"]` | | `integration` *(Optional)* | String | Identifier of third-party platform to skip Merge Link menu for. See [single integration](/merge-unified/authentication/single-integration) guide. | | `link_expiry_mins` *(Optional)* | Integer | An integer number of minutes between \[30, 720 or 10080 if for a Magic Link URL] for how long this token is valid. Defaults to 30. | | `should_create_magic_link_url` *(Optional)* | Boolean | Whether to generate a Magic Link URL. Defaults to false. For more information on Magic Link, see [Magic Link](/merge-unified/authentication/magic-link) guide. | | `completed_account_initial_screen` *(Optional)* | Enum | Identifier of the page that Merge Link should open to for a completed Linked Account. When using this field, you must pass in the category of that Linked Account to `categories`. Allowed values: `["SELECTIVE_SYNC"]`. Defaults to null. | ### API response The response will include the following fields: | Parameter | Type | Description | | ------------------ | ------ | ----------------------------------------------------------------------------- | | `link_token` | String | Temporary token to initialize your end user's Merge Link. | | `integration_name` | String | The name of any previously connected third-party platform (otherwise `null`). | | `magic_link_url` | String | The URL of the magic link if specified (otherwise `null`). | Pass the `link_token` to your frontend to display Merge Link in step 2. ```python import requests # Replace api_key with your Merge production API Key def create_link_token(user, api_key): body = { "end_user_origin_id": user.organization.id, # unique entity ID "end_user_organization_name": user.organization.name, # your user's organization name "end_user_email_address": user.email_address, # your user's email address "categories": ["hris", "ats", "accounting", "ticketing", "crm"], # choose your category } headers = {"Authorization": f"Bearer {api_key}"} link_token_url = "https://api.merge.dev/api/integrations/create-link-token" link_token_result = requests.post(link_token_url, data=body, headers=headers) link_token = link_token_result.json().get("link_token") return link_token ``` **`Ruby`** ```ruby Ruby require 'httparty' # Replace api_key with your organization's production API Key def create_link_token(user, api_key) uri = URI('https://api.merge.dev/api/integrations/create-link-token') body = { 'end_user_origin_id' => user.organization.id, # unique entity ID 'end_user_organization_name' => user.organization.name, # your user's organization name 'end_user_email_address' => user.email_address, # your user's email address 'categories' => ["hris", "ats", "accounting", "ticketing", "crm"], # choose your category } headers = { 'Authorization' => "Bearer #{api_key}", 'Content-Type' => 'application/json' } link_token_response = HTTParty.post(uri, body: body.to_json, headers: headers) link_token_response['link_token'] end ``` **`Node`** ```javascript Node import { MergeClient, Merge } from '@mergeapi/merge-node-client'; // Swap YOUR_API_KEY below with your production key from: // https://app.merge.dev/keys const merge = new MergeClient({apiKey: 'YOUR_API_KEY'}); const linkTokenResponse = await merge.ats.linkToken.create({ endUserEmailAddress: user.email_address, endUserOrganizationName: user.organization.name, endUserOriginId: user.id, categories: [Merge.hris.CategoriesEnum.Hris, Merge.ats.CategoriesEnum.Ats, Merge.filestorage.CategoriesEnum.Filestorage], linkExpiryMins: 30}); console.log("Created link token", linkTokenResponse.linkToken); ``` **`Java`** ```java Java import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.node.ObjectNode; import java.io.IOException; import java.net.URI; import java.net.http.HttpClient; import java.net.http.HttpRequest; import java.net.http.HttpResponse; import java.util.HashMap; import java.util.Map; public class MergeLink { public static String createLinkToken(User user, String apiKey) throws IOException, InterruptedException { Map data = new HashMap(); data.put("end_user_origin_id", user.organization.id); // unique entity ID data.put("end_user_organization_name", user.organization.name); // your user's organization name data.put("end_user_email_address", user.email_address); // your user's email address String[] categories = { "hris", "ats", "accounting", "ticketing" }; data.put("categories", categories); ObjectMapper mapper = new ObjectMapper(); String requestBody = mapper.writeValueAsString(data); HttpClient client = HttpClient.newHttpClient(); URI uri = URI.create("https://api.merge.dev/api/integrations/create-link-token"); HttpRequest request = HttpRequest.newBuilder(uri).header("Content-Type", "application/json") .header("Authorization", String.format("Bearer %s", apiKey)) .POST(HttpRequest.BodyPublishers.ofString(requestBody)).build(); HttpResponse linkTokenResult = client.send(request, HttpResponse.BodyHandlers.ofString()); String linkToken = new ObjectMapper().readValue(linkTokenResult.body(), ObjectNode.class).get("link_token") .textValue(); return linkToken; } } ``` **`Elixir`** ```elixir Elixir # Replace api_key with your organization's production API Key def create_link_token(user, api_key) do uri = "https://api.merge.dev/api/integrations/create-link-token" body = %{ # unique entity ID "end_user_origin_id" => user.organization.id, # your user's organization name "end_user_organization_name" => user.organization.name, # your user's email address "end_user_email_address" => user.email, # choose your category "categories" => ["hris", "ats", "accounting", "ticketing", "crm"], } |> Jason.encode!() {:ok, response} = Tesla.post(uri, body, headers: [{"content-type", "application/json"}, {"Authorization", "Bearer #{api_key}"}] ) link_token_response = Jason.decode!(response.body) link_token_response["link_token"] end ``` > **Note** > > Each `end_user_origin_id` can have a maximum of **one Linked Account per category**. For example, each ID can have up to one HRIS, one ATS, one Accounting, one Ticketing, one CRM, and one File Storage integration simultaneously. If you want to link multiple accounts for the same user, learn more in our [help center](https://help.merge.dev/en/articles/5389804-can-i-have-multiple-linked-accounts-per-customer-within-a-single-category). --- #### Frontend – Make Merge Link appear In your frontend, use the `link_token` from step 1 to open Merge Link. ### Display Merge Link Pass in these parameters: | Parameter | Type | Description | | --------------------------- | -------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `linkToken` | String | Initializing token from step 1. | | `onSuccess` | Function | Callback to handle `public_token`, which is returned when your end user finishes their Merge Link session. Use it immediately to swap for your account token. | | `onExit` *(Optional)* | Function | Callback to handle when your end user closes Merge Link. You can add your own logic here to define any functionality. | | `tenantConfig` *(Optional)* | Object | Parameter to specify an `apiBaseURL` for a tenant. For example, for the EU multi-tenant, `apiBaseURL` should be set to `https://api-eu.merge.dev`. The default value is `https://api.merge.dev`. | Pass the `public_token` to your backend to securely swap it for an `account_token` in step 3. **`React`** ```typescript React import React, { useCallback } from "react"; // In your React project folder, run: // npm install --save @mergeapi/react-merge-link import { useMergeLink } from "@mergeapi/react-merge-link"; const App = () => { const onSuccess = useCallback((public_token) => { // Send public_token to server (Step 3) }, []); const { open, isReady } = useMergeLink({ linkToken: "ADD_GENERATED_LINK_TOKEN", // Replace ADD_GENERATED_LINK_TOKEN with the token retrieved from your backend (Step 1) onSuccess, // tenantConfig: { // apiBaseURL: "https://api-eu.merge.dev" /* OR your specified single tenant API base URL */ // }, }); return ( ); }; export default App; ``` **`Vue`** ```vue Vue ``` **`HTML/JS`** ```html HTML/JS ``` --- #### Backend – Swap public token for account token In your backend, create a request to exchange the short-lived `public_token` for a permanent `account_token`. > **Note** > > **Important:** Securely store this `account_token` in your database for authenticating future API requests to the Unified API regarding the end user's data. ```python import requests def retrieve_account_token(public_token, api_key): headers = {"Authorization": f"Bearer {api_key}"} account_token_url = "https://api.merge.dev/api/integrations/account-token/{}".format(public_token) account_token_result = requests.get(account_token_url, headers=headers) account_token = account_token_result.json().get("account_token") return account_token # Save this in your database ``` **`Ruby`** ```ruby Ruby ``` **`Node`** ```javascript Node import { MergeClient, Merge } from '@mergeapi/merge-node-client'; // Swap YOUR_API_KEY below with your production key from: // https://app.merge.dev/keys const merge = new MergeClient({apiKey: 'YOUR_API_KEY'}); const accountTokenResponse = await merge.ats.accountToken.retrieve("END_USER_PUBLIC_TOKEN"); console.log("Created account token", accountTokenResponse.accountToken); ``` **`Java`** ```java Java import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.node.ObjectNode; import java.io.IOException; import java.net.URI; import java.net.http.HttpClient; import java.net.http.HttpRequest; import java.net.http.HttpResponse; import java.util.HashMap; import java.util.Map; public class MergeLinkingFlow { public static String retrieveAccountToken(String publicToken, String apiKey) throws IOException, InterruptedException { HttpClient client = HttpClient.newHttpClient(); URI uri = URI.create(String.format("https://api.merge.dev/api/integrations/account-token/%s", publicToken)); HttpRequest request = HttpRequest.newBuilder(uri) .header("Authorization", String.format("Bearer %s", apiKey)) .build(); HttpResponse account_token_result = client.send(request, HttpResponse.BodyHandlers.ofString()); String account_token = new ObjectMapper().readValue(account_token_result.body(), ObjectNode.class).get("account_token").textValue(); return account_token; } } ``` --- ## What to do next You now have a production-shape Merge Link integration. Your end users can connect their systems and you receive an `account_token` for each. Next: Architecture reference Or jump to [webhooks](/merge-unified/reading-data/webhooks/overview), [syncing best practices](/merge-unified/reading-data/syncing-best-practices), or [writing data](/merge-unified/writing-data/overview). > Build once. Connect to every API, tool, and LLM. Ship products and agents to production without owning the integration stack.