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

# API overview

> Reference for the Agent Handler REST API: base URL, auth header format, error shape, rate limits, idempotency, and pagination.

The Agent Handler REST API is the management plane - your backend uses it to create Registered Users, manage Tool Packs, mint Link tokens, and configure the rest of your account. The MCP server is a separate surface (it's what your agent talks to); see [MCP integration](/merge-agent-handler/build/connecting-agents/mcp-integration) for that side.

This page covers the conventions that apply across every endpoint. Per-endpoint reference is in the sidebar - Connectors, Credentials, Tool Packs, Registered Users, and so on.

## Base API URL

All API endpoints in the reference documentation are relative to the following base URL:

```
https://ah-api.merge.dev/api/v1
```

If you are using our EU tenant, all API endpoints in the reference documentation are relative to the following base URL:

```
https://ah-api-eu.merge.dev/api/v1
```

## Authentication

Every request needs an [Access Key](/merge-agent-handler/administer/access-keys) in the `Authorization` header:

```
Authorization: Bearer <YOUR_API_KEY>
```

The word `Bearer` is required. Tokens are not URL parameters - never put them in the path or query string.

Two key environments:

* **Production keys** call against production data. Any number, and each one can be scoped to specific Tool Packs or Registered Users.
* **Test keys** call against a sandbox. Test Registered Users, test credentials, isolated audit log. Any number of test keys.

Mixing them produces `404 not_found` on resources that exist in the other environment. Pick the right one for the call you're making.

## Errors

Every error response uses the same shape:

```json
{
  "error": {
    "type": "validation_error",
    "code": "origin_user_id_required",
    "message": "origin_user_id is required when creating a Registered User.",
    "documentation_url": "https://docs.merge.dev/merge-agent-handler/resources/troubleshooting"
  }
}
```

| HTTP status             | When it fires                                                                                                                                                        |
| ----------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `400` Bad Request       | Validation failure. The `code` field names the specific check (`origin_user_id_required`, `invalid_company_for_shared_scope`). Don't retry without fixing the input. |
| `401` Unauthorized      | Missing or invalid API key. Don't retry - fix the credential.                                                                                                        |
| `403` Forbidden         | Authenticated but not allowed. Tool the Registered User can't call, resource in the wrong environment, role missing the permission. Don't retry.                     |
| `404` Not Found         | Resource doesn't exist (or exists in the other environment).                                                                                                         |
| `409` Conflict          | Idempotency conflict - e.g., creating a Registered User with an `origin_user_id` already taken in the same environment. The body includes the existing resource.     |
| `429` Too Many Requests | Rate limit hit. Retry with backoff after the duration in the `Retry-After` header.                                                                                   |
| `5xx`                   | Agent Handler issue. Retry with exponential backoff.                                                                                                                 |

Errors are stable - when we add a new variant, we add a new `code` value rather than changing existing ones, so you can match on `code` safely.

## Rate limits

Default limits per organization:

| Endpoint family                             | Limit                                                                                                       |
| ------------------------------------------- | ----------------------------------------------------------------------------------------------------------- |
| Read endpoints (`GET`)                      | 600 requests per minute                                                                                     |
| Write endpoints (`POST`, `PATCH`, `DELETE`) | 120 requests per minute                                                                                     |
| MCP tool calls                              | Limited per organization and per Registered User, so one user cannot saturate the organization's throughput |

When you exceed a limit, the response is `429` with these headers:

```
Retry-After: 12
X-RateLimit-Limit: 600
X-RateLimit-Remaining: 0
X-RateLimit-Reset: 1714838531
```

Honor `Retry-After`. Backing off by the suggested amount keeps you well-behaved; aggressive retry through a `429` will keep getting `429`s until the bucket refills.

These limits are separate from your monthly credit allotment, which is what [Billing and usage](/merge-agent-handler/administer/billing) covers: a `429` means you are calling too fast, a `402` means you are out of credits. Contact your account team if the defaults aren't enough.

## Idempotency

`POST` endpoints that create resources are idempotent on a stable client-supplied key:

* **`/registered-users`** - idempotent on `origin_user_id`. A repeat call with the same `origin_user_id` returns the existing Registered User (status `200`), not a duplicate (status `201`).
* **`/registered-users/{id}/link-token`** - not idempotent. Each call mints a new link token; tokens are single-use and short-lived.
* **`/credentials/export`** - not idempotent.

For endpoints that aren't idempotent on a natural key, you can pass a request-scoped `Idempotency-Key` header with any unique value. Repeat requests with the same key within a 24-hour window return the original response without re-executing.

```
Idempotency-Key: 7b8e8c01-9c0e-44b3-b7a2-2b5d2f7d8e1c
```

Use UUIDs or any other globally-unique value. Don't reuse keys across logically distinct calls.

## Pagination

List endpoints are cursor-paginated. The response shape is consistent across resources:

```json
{
  "data": [ /* up to `limit` items */ ],
  "next_cursor": "eyJpZCI6ICJmOTgxM2RkNS0...",
  "has_more": true
}
```

Pass `next_cursor` as the `cursor` query parameter on the next request. When `has_more` is `false`, you've reached the end.

Default `limit` is 50; max is 250. The cursor is opaque - don't try to decode or construct your own.

## Versioning

The current API version is `v1`. New, backwards-compatible fields and endpoints are added without bumping the version. Breaking changes (renaming a field, removing an endpoint, changing semantics) ship under `v2` - both versions run side by side during a migration window.

When breaking changes are coming, you'll see them announced in the changelog with at least 90 days of notice and a migration guide.

## Webhooks

Outbound webhooks fire from a separate domain (`https://ah-api.merge.dev/webhooks/...` is for inbound; outbound originates from Agent Handler to your endpoint). Verify signatures before trusting payloads - see [Webhooks](/merge-agent-handler/observe/webhooks) for the full guide.

## SDKs

Official SDKs are in development. For now, the API is callable directly with any HTTP client - examples in the per-endpoint reference cover cURL, Python, and TypeScript.

For MCP-side integration (the agent's view, not the management API), see [MCP integration](/merge-agent-handler/build/connecting-agents/mcp-integration).

## Status and support

API status, incidents, and uptime live at [status.merge.dev](https://status.merge.dev). For support, file from the dashboard - include the request ID from the `X-Request-ID` header on the response and we can find your call in our logs immediately.