> This page is for Agent Handler.

> For clean Markdown of any page, append .md to the page URL.
> For a complete documentation index, see https://docs.merge.dev/llms.txt.
> For AI client integration (Claude Code, Cursor, etc.), connect to the MCP server at https://docs.merge.dev/_mcp/server.

# Link

> How to integrate Agent Handler's Link component: when it runs, how to embed it, callback handling, error states.

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](/merge-agent-handler/build/authentication/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.

```bash
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" }'
```

```json
{
  "link_token": "ltk_•••••",
  "expires_at": "2026-05-04T18:30:00Z"
}
```

The `Connector` field is the Connector slug - lowercase with hyphens, like `google-drive`, `microsoft-teams`, `salesforce`. The full list is in the [Connector catalog](/merge-agent-handler/Connectors).

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:

```bash
npm install @mergeapi/react-agent-handler-link
```

Open Link with the token your backend just minted:

**`ConnectButton.tsx`**

```tsx ConnectButton.tsx
import { useAgentHandlerLink } from "@mergeapi/react-agent-handler-link";

export function ConnectSlackButton({ linkToken }: { linkToken: string }) {
  const { open, isReady } = useAgentHandlerLink({
    linkToken,
    onSuccess: (event) => {
      // Credentials stored. event.Connector is the slug; event.scopes the granted scopes.
      // Refresh your UI, or kick off the agent action.
    },
    onExit: (error) => {
      // User closed Link or auth failed.
      // error is null on a clean exit, populated on failure.
    },
  });

  return (
    <button disabled={!isReady} onClick={() => open()}>
      Connect Slack
    </button>
  );
}
```

`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](/merge-agent-handler/build/authentication/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:

```json
{
  "type": "authenticate_meta",
  "Connector": "slack",
  "link_token": "ltk_•••••",
  "expires_at": "2026-05-04T18:30:00Z",
  "message": "Please connect Slack to continue."
}
```

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:

```tsx
for await (const event of agentStream) {
  if (event.type === "function_call_result" && event.result?.type === "authenticate_meta") {
    setPendingLinkToken(event.result.link_token);
    break; // Stop streaming; resume after auth.
  }
}
```

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](https://ah.merge.dev/settings/api-keys), 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](/merge-agent-handler/resources/troubleshooting).

## Common errors

| Symptom                                     | Cause                              | Fix                                                                        |
| ------------------------------------------- | ---------------------------------- | -------------------------------------------------------------------------- |
| `open()` does nothing                       | Link token expired (30-min window) | Generate a fresh token per session                                         |
| `open()` fires but Link flashes and closes  | Token already used                 | Tokens are single-use; generate a new one                                  |
| OAuth callback "unauthorized origin"        | Frontend domain not on allow-list  | Add to [Allowed callback origins](https://ah.merge.dev/settings/api-keys)  |
| `onSuccess` fires but tool calls still fail | Token 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](https://ah.merge.dev/configure/link). See [Link customization](/merge-agent-handler/build/authentication/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](/merge-agent-handler/build/authentication/magic-link) instead.