Magic Link

Let your agent share a URL for users to authenticate — no frontend component needed.


Instead of embedding the Link component in a frontend, your agent can get a URL back that it shares directly with users. The user opens it in a browser, authenticates, and closes the tab.

No changes are needed on your end to start using this — the magic link URL is returned automatically.

When you call POST /registered-users/{id}/link-token, the response now includes a magic_link_url field:

1{
2 "link_token": "abc123...",
3 "magic_link_url": "https://ah-api.merge.dev/magic-link/..."
4}

Your agent can send that URL to the user however it likes — in a chat message, email, or in-app notification. The user clicks it, connects their account, and clicks “Done” to close the tab.

The link_token field still works exactly as before if you want to embed the Link component yourself.

Authenticate tools (MCP / agent tool calls)

When your agent calls an authenticate_* tool, the result payload now includes a message and magic link URL:

1{
2 "message": "The user must authenticate with Linear. Share this link with the user to connect their account: https://ah-api.merge.dev/magic-link/... Once authenticated, you must refetch tools or restart your MCP connection.",
3 "magic_link_url": "https://ah-api.merge.dev/magic-link/...",
4 "link_token": "abc123..."
5}
FieldDescription
messageAn instruction for the agent — tells it to share the link with the user and what to do after authentication.
magic_link_urlThe URL to share with the user for authentication.
link_tokenThe link token, still included for backwards compatibility with the embedded Link component.

Callback URLs

You can automatically redirect users back to your application after they complete authentication via a Magic Link. This is especially useful for mobile deep linking and desktop apps.

1. Register allowed origins

Before using callback URLs, register the allowed origins for your organization in the Merge dashboard under Settings → API keys → Allowed callback origins.

An origin is the scheme and host of your callback URL. Valid origins include:

OriginUse case
https://myapp.comWeb application
https://app.example.com:8443Web app on a custom port
myapp://Mobile deep link (iOS / Android)
tauri://Desktop app (Tauri, Electron, etc.)
HTTP origins are not allowed. Use HTTPS for web applications or a custom scheme for native apps.

Add a callback_url parameter to your link token request body:

1POST /registered-users/{id}/link-token
2
3{
4 "connector_slug": "linear",
5 "callback_url": "https://myapp.com/integrations/done"
6}

The origin of the callback_url (e.g. https://myapp.com) must match one of the origins you registered. If it doesn’t match, the request will fail with a validation error.

The callback URL is stored with the link configuration and automatically used when the user completes authentication. You don’t need to append it as a query parameter to the magic link URL.

3. Handle the redirect

After the user completes (or exits) the authentication flow, they are redirected to your callback URL. Merge automatically appends query parameters to indicate the result. These parameters are added by Merge during the redirect — you don’t need to include them in your callback URL.

The following parameters are appended to your callback URL:

ParameterDescription
statusAlways included. Authentication status: success, error, or exit. See status values below
codeOptional. Authorization code included only when using authorization code exchange. See Authorization Code Exchange
stateOptional. Your custom state value, included only if you provided it during link token creation. See Authorization Code Exchange

Status values:

StatusMeaning
successUser completed authentication successfully
errorAn error occurred during authentication
exitUser closed or exited the flow without completing

Example redirect URLs:

Basic flow (credential created immediately):

https://myapp.com/integrations/done?status=success

Secure flow with authorization code (when state is provided):

https://myapp.com/integrations/done?status=success&code=ah_code_xyz789...&state=custom-value

Register your app’s custom URL scheme as an allowed origin (myapp://), then include it in your link token request:

1POST /registered-users/{id}/link-token
2
3{
4 "connector_slug": "linear",
5 "callback_url": "myapp:///integrations/complete"
6}

After authentication, the browser redirects to myapp:///integrations/complete?status=success, which opens your native app via its registered URL scheme.

Authorization Code Exchange

Use server-to-server code exchange instead of creating credentials immediately in the browser. This adds an extra security layer by letting your backend verify the credential belongs to the right user before it’s saved.

How it works:

  1. Include a state parameter when calling POST /registered-users/{id}/link-token
  2. User authenticates and is redirected to your callback URL with a code and state
  3. Your backend exchanges the code for a credential via POST /api/v1/link-token/confirm/

Include both state and callback_url when creating a link token:

1POST /registered-users/{id}/link-token
2
3{
4 "connector_slug": "linear",
5 "callback_url": "https://myapp.com/integrations/callback",
6 "state": "random-csrf-token-123"
7}

Both parameters are required:

  • state triggers authorization code creation
  • callback_url delivers the code to your backend

Don’t include query parameters like ?state=... in your callback_url. We’ll automatically append status, code, and state during the redirect.

The response includes the magic link URL:

1{
2 "link_token": "abc123...",
3 "magic_link_url": "https://ah-api.merge.dev/magic-link/..."
4}

Receive the authorization code

After authentication completes, your callback URL receives the code:

https://myapp.com/integrations/callback?status=success&code=ah_code_xyz789...&state=random-csrf-token-123

Always validate that the state parameter matches your original value to prevent CSRF attacks.

Exchange the code

Call the confirmation endpoint from your backend:

1POST /api/v1/link-token/confirm/
2Authorization: ProductionKey <your-production-key>
3Content-Type: application/json
4
5{
6 "code": "ah_code_xyz789..."
7}

Response:

1{
2 "credential_id": "134e0111-0f67-44f6-98f0-597000290bb3",
3 "connector_slug": "linear",
4 "registered_user_id": "550e8400-e29b-41d4-a716-446655440000"
5}

The credential is created and ready. The code is deleted after exchange.

Security

  • Codes expire after 5 minutes — exchange them promptly
  • Each code can only be exchanged once
  • The confirm endpoint requires ProductionKey authentication
  • Codes can only be exchanged by the organization that created them
  • Always validate the state parameter matches your original value

Error handling

StatusErrorMeaning
400Invalid authorization codeCode doesn’t exist or was used
400Authorization code has expiredCode is older than 5 minutes
400Authorization code already usedCode was exchanged already
403Code does not belong to your organizationWrong ProductionKey

When to use

Use authorization code exchange when you need server-to-server credential creation for security or compliance requirements, or when building mobile/desktop apps with a backend.

For simpler flows where the user authenticates and closes the tab, omit the state parameter — credentials are created immediately.