For AI agents: a documentation index is available at the root level at /llms.txt and /llms-full.txt. Append /llms.txt to any URL for a page-level index, or .md for the markdown version of any page.
Logo
Resources
Log inGet a demo
Get startedConnectorsAPI reference
Get startedConnectorsAPI reference
    • Overview
    • How it works
    • Quickstart
    • Use cases
  • Setup
    • Building an agent
    • Context layer for employees
    • Local development
  • Build
      • Link
      • Magic Link
      • Link customization
  • Secure
    • Security Gateway
    • Standard Entity Rules
    • Custom Regex Rules
    • Violations and alerts
    • Rule Tester
  • Observe
    • Tool Call Logs
    • API Request Logs
    • Audit Trail
    • Webhooks
    • Playground
  • Administer
    • Team and roles
    • Single sign-on
    • SCIM provisioning
    • Multi-factor authentication
    • Access Keys
    • Application Credentials
    • Billing and usage
  • Resources
    • Troubleshooting
    • FAQ

Get started

  • Overview
  • Introduction
  • Unified API
  • Linked Account
  • Merge Link
  • Use cases

Implementation

  • Sandboxes
  • SDKs
  • API access
  • Syncing data
  • Writing data
  • Data minimization
  • Supplemental data
  • Errors
  • Integration metadata

API reference

  • ATS
  • HRIS
  • Accounting
  • Ticketing
  • CRM
  • File Storage
  • Knowledge Base
  • Chat

Resources

  • Help Center
  • Merge.dev
  • Changelog
© Merge 2026Terms of usePrivacy policy
UnifiedAgent HandlerGateway
UnifiedAgent HandlerGateway
Resources
Log inGet a demo
On this page
  • When Link runs
  • Generate a link token
  • Open Link from your frontend
  • The runtime trigger pattern
  • Callback URLs and CORS
  • Common errors
  • Customizing what Link looks like
  • Next
BuildAuthentication

Link

The embedded UI your end users authenticate Connectors through.
Was this page helpful?
Previous

Companies and Groups

Next

Magic Link

Link is the embedded authentication UI. Your product opens Link, the user picks a Connector, completes OAuth (or pastes an API key), lands back in your app. The result is a credential stored against their Registered User - Agent Handler holds it; your code never sees it.

This page covers Link in your product. For email-driven flows or any context where you can’t render a React component, use Magic Link instead.

When Link runs

Two trigger patterns; pick one or both.

Pre-flight, from a settings page. The user clicks “Connect Slack” in your UI before the agent ever runs. Most B2B products do this - users prefer setting up integrations once rather than being interrupted mid-conversation. Your backend mints a link token; your frontend opens Link.

At agent runtime. The agent calls a tool the user hasn’t authenticated. The Connector returns an authenticate_meta tool result with a link_token. Your frontend catches it, opens Link, the user authenticates, the original tool call retries. No separate “missing auth” UI needed.

Generate a link token

Backend call. Mint one link token per Link session. Tokens are single-use and expire after 30 minutes.

$curl -X POST https://ah-api.merge.dev/api/registered-users/$REGISTERED_USER_ID/link-token \
> -H "Authorization: Bearer $MERGE_AGENT_HANDLER_API_KEY" \
> -H "Content-Type: application/json" \
> -d '{ "Connector": "slack" }'
1{
2 "link_token": "ltk_•••••",
3 "expires_at": "2026-05-04T18:30:00Z"
4}

The Connector field is the Connector slug - lowercase with hyphens, like google-drive, microsoft-teams, salesforce. The full list is in the Connector catalog.

If you want the user to pick from a list of Connectors instead of locking to one, omit Connector from the request body. Link will show the picker for every Connector configured on the Tool Pack.

Open Link from your frontend

Install the React component:

$npm install @mergeapi/react-agent-handler-link

Open Link with the token your backend just minted:

ConnectButton.tsx
1import { useAgentHandlerLink } from "@mergeapi/react-agent-handler-link";
2
3export function ConnectSlackButton({ linkToken }: { linkToken: string }) {
4 const { open, isReady } = useAgentHandlerLink({
5 linkToken,
6 onSuccess: (event) => {
7 // Credentials stored. event.Connector is the slug; event.scopes the granted scopes.
8 // Refresh your UI, or kick off the agent action.
9 },
10 onExit: (error) => {
11 // User closed Link or auth failed.
12 // error is null on a clean exit, populated on failure.
13 },
14 });
15
16 return (
17 <button disabled={!isReady} onClick={() => open()}>
18 Connect Slack
19 </button>
20 );
21}

isReady flips to true once the component has loaded the token and is ready to open. Disable your button until then to avoid no-op clicks.

For projects without React, the same UI is reachable via a hosted URL: https://ah.merge.dev/link/<LINK_TOKEN>. Open it in a popup or redirect; on success, your redirect_uri (configured in Link customization) gets hit with the result.

The runtime trigger pattern

When the agent calls a tool the user hasn’t authenticated, Agent Handler returns a special authenticate_meta payload instead of failing the call:

1{
2 "type": "authenticate_meta",
3 "Connector": "slack",
4 "link_token": "ltk_•••••",
5 "expires_at": "2026-05-04T18:30:00Z",
6 "message": "Please connect Slack to continue."
7}

Your frontend should intercept the streamed tool-call result, detect the authenticate_meta shape, and open Link with the embedded token. After onSuccess fires, retry the user’s original prompt - the agent’s next call to the same tool will succeed because credentials are now stored.

Pattern in TypeScript, against an Anthropic-style stream:

1for await (const event of agentStream) {
2 if (event.type === "function_call_result" && event.result?.type === "authenticate_meta") {
3 setPendingLinkToken(event.result.link_token);
4 break; // Stop streaming; resume after auth.
5 }
6}

Then render <ConnectButton linkToken={pendingLinkToken} onSuccess={() => resumeAgent()} />.

Callback URLs and CORS

Link redirects through the third party’s OAuth consent screen and back. The URL the third party redirects to has to be on Agent Handler’s allow-list - that’s a security check.

In Settings → API Keys → Allowed callback origins, add every domain that hosts your frontend. The match is exact - app.example.com and staging.example.com need separate entries. Localhost ports for development need entries too.

If you skip this, you’ll see “unauthorized origin” on the callback. See Troubleshooting → Allowed callback origin error.

Common errors

SymptomCauseFix
open() does nothingLink token expired (30-min window)Generate a fresh token per session
open() fires but Link flashes and closesToken already usedTokens are single-use; generate a new one
OAuth callback “unauthorized origin”Frontend domain not on allow-listAdd to Allowed callback origins
onSuccess fires but tool calls still failToken storage delayed (rare)Retry after a short backoff, or rely on the authenticate_meta retry path

Customizing what Link looks like

Logo, colors, header copy, redirect URL, allowed Connectors - all configurable per organization at Configure → Link. See Link customization for the asset specs and what end users see if you configure nothing.

Next

For email and mobile flows where you can’t render a React component, use Magic Link instead.