> 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.

# Scoping access per user

> How to scope Agent Handler access per end user: mint a short-lived user-scoped Access Key from your production key, what the runtime and management boundaries enforce, and why a Registered User ID in the MCP URL cannot be used to reach another user's data.

Agent Handler has two kinds of credential, and the difference between them is usually the first thing a security review asks about.

Your production Access Key is organization-wide. It reaches every Tool Pack and every Registered User you own. That is the right credential for your backend to hold and the wrong one for an agent session to carry. A **user-scoped Access Key** is minted from it, names one Registered User, and cannot call tools as anyone else.

Reach for this page when your agent serves more than one person, when a downstream service needs its own credential, or when someone asks you to prove that one customer's agent cannot read another customer's data.

## The exchange

Your backend authenticates once with the organization-wide key, then hands the agent a narrower one. Three steps.

**1. Hold the production key in your backend.** It lives in your secret store and never reaches the agent, the browser, or a log line. See [Access Keys](/merge-agent-handler/administer/access-keys) for storage and rotation.

**2. Mint a user-scoped key when a session starts.** Name the Registered User the session belongs to, the Tool Packs it should reach, and a short expiry:

```bash
curl -X POST https://ah-api.merge.dev/api/v1/access-keys/ \
  -H "Authorization: Bearer $MERGE_AGENT_HANDLER_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "session-user_a3f9b2",
    "registered_user_ids": ["f9813dd5-e70b-484c-91d8-00acd6065b07"],
    "tool_pack_ids": ["1d8b6f4a-1b3e-4f9b-9d3a-3a8e8b9c0d1f"],
    "scopes": ["runtime:all"],
    "expires_in": 3600
  }'
```

```json
{
  "id": "3c90c3c0-6d46-4b50-8888-8dd25736052f",
  "name": "session-user_a3f9b2",
  "key": "sk_live_xyz789...",
  "key_masked": "sk_live_xyz...c4d1",
  "registered_user_ids": ["f9813dd5-e70b-484c-91d8-00acd6065b07"],
  "tool_pack_ids": ["1d8b6f4a-1b3e-4f9b-9d3a-3a8e8b9c0d1f"],
  "scopes": ["runtime:all"],
  "is_test": false,
  "expires_at": "2026-04-29T13:00:00Z"
}
```

`key` is returned once, on this response. After that only `key_masked` is readable, so pass it to the agent now or mint a new one.

**3. Call MCP with the scoped key.** The agent carries it in the `Authorization` header for the life of the session. Nothing else about the [MCP call](/merge-agent-handler/build/connecting-agents/mcp-integration) changes.

## What the scoped key cannot do

Two independent boundaries, enforced server-side on every request.

| Boundary       | Set by                                 | What it stops                                                         |
| -------------- | -------------------------------------- | --------------------------------------------------------------------- |
| **Runtime**    | `registered_user_ids`, `tool_pack_ids` | Tool calls for any Registered User or Tool Pack the key does not name |
| **Management** | `scopes`                               | Reading or writing configuration, when `management:all` is left off   |

`registered_user_ids` is checked on every tool call, against the key rather than against the URL. The MCP endpoint carries a Registered User ID in its path:

```http
https://ah-api.merge.dev/api/v1/tool-packs/<TOOL_PACK_ID>/registered-users/<REGISTERED_USER_ID>/mcp
```

Editing that segment to a Registered User the key does not name fails the call with `404`, carrying `Resource not in API key scope.` It does not return the other user's data, and it does not fall back to the key's own user. Whoever holds a user-scoped key holds access to exactly the users named on it, so the path segment is a selector within the key's grant, never a way to widen it.

The status is `404` rather than `403` on purpose: an out-of-scope Registered User is reported the same way as one that does not exist, so a key cannot be used to enumerate what you own. Expect the same on a Tool Pack the key does not name, and on `search_tools` as well as `tools/call`.

> **Note**
>
> Scopes are the reason to mint keys through the API rather than restrict them in the dashboard. The dashboard restricts Tool Packs and Registered Users but does not let you choose scopes, so a key restricted there still reaches the management endpoints. `POST /api/v1/access-keys/` lets you pass `["runtime:all"]` and leave `management:all` off, which is what makes a session key unable to read or change configuration at all.

## Narrower only, never wider

A key minted through the API can only be narrower than the key that minted it. `tool_pack_ids`, `registered_user_ids`, `scopes`, and the expiry each have to be a subset of the calling key's own grant. A key scoped to one Registered User cannot mint a key that reaches a second one, so a leaked session key cannot be escalated into a broader one.

Omitting a field is not the same as passing `null`. `null` means unrestricted on that dimension, and it is only accepted when the calling key is itself unrestricted there. Name the users and packs explicitly on any key an agent will hold.

## Expiry

`expires_in` takes seconds and `expires_at` takes a timestamp. Omit both and the key expires in 90 days, which is far longer than a session needs. Match the expiry to the session: an hour is a reasonable default, and a short-lived key limits how long a leaked value is worth anything.

The expiry is also part of the subset rule, so a one-hour session key cannot rotate or revoke your never-expiring production key. See [which keys a key can rotate or revoke](/merge-agent-handler/administer/access-keys#which-keys-a-key-can-rotate-or-revoke).

## Auditing it

Every tool call a scoped key makes is attributed to its Registered User in [Tool Call Logs](/merge-agent-handler/observe/tool-call-logs), searchable by `origin_user_id`. Key creation, regeneration, and revocation land in the [Audit Trail](/merge-agent-handler/observe/audit-trail), and a rotation entry names both the acting key and the target key, so you can read a chain without correlating requests by hand.

## Next

Rotation, storage, and the dashboard workflow live on [Access Keys](/merge-agent-handler/administer/access-keys).