> This page is for Gateway.

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

# Management API keys

> Create and administer your organization's API keys programmatically with a management key: model allowlists, routing policy bindings, per-key spend limits, and per-key usage.

The Management API issues, rotates, restricts, and caps your organization's API keys from code, such as one key per customer or employee. It authenticates with a management key that can never make model calls.

## Management keys

Create management keys under **API keys → Management keys** in the [dashboard](https://gateway.merge.dev/api-keys), the only place they're made. The raw `mgmt_` key is shown once, acts on one organization, returns `401` on model endpoints, and should stay server-side. New keys carry all four scopes; older keys need a re-mint to gain newer ones.

| Scope             | Grants                                                     |
| ----------------- | ---------------------------------------------------------- |
| `manage_keys`     | Create, update, and delete API keys                        |
| `read_usage`      | Read keys and usage                                        |
| `manage_projects` | The [Projects API](/merge-gateway/automation/projects-api) |
| `manage_routing`  | Organization routing policies                              |

Send it to `https://api-gateway.merge.dev` as `Authorization: Bearer mgmt_...`.

## Create a key

`POST /v1/keys` returns `201` with the raw `mg_` key, exactly once. All body fields are optional.

| Field                  | Meaning                                                                                                      |
| ---------------------- | ------------------------------------------------------------------------------------------------------------ |
| `name`                 | Label shown in the key list and usage views, such as `customer-acme`                                         |
| `limit`, `limit_reset` | [Spend limit](#per-key-spend-limits) in USD (greater than 0) and its window: `daily`, `weekly`, or `monthly` |
| `project_id`           | Attributes every request to that [project](/merge-gateway/cost/projects). Can't change after creation        |
| `allowed_models`       | [Model allowlist](#restrict-which-models-a-key-can-call)                                                     |

Creation returns `403 phone_verification_required` until your organization verifies a phone number, and `403 payment_method_required` when a non-Free organization has no payment method on file.

**`cURL`**

```bash title="cURL"
curl -X POST https://api-gateway.merge.dev/v1/keys \
  -H "Authorization: Bearer $MERGE_GATEWAY_MANAGEMENT_KEY" \
  -H "Content-Type: application/json" \
  -d '{"name": "customer-acme", "limit": 50, "limit_reset": "monthly"}'
```

**`Python`**

```python title="Python"
import os
import requests

resp = requests.post(
    "https://api-gateway.merge.dev/v1/keys",
    headers={"Authorization": f"Bearer {os.environ['MERGE_GATEWAY_MANAGEMENT_KEY']}"},
    json={"name": "customer-acme", "limit": 50, "limit_reset": "monthly"},
)
key = resp.json()
print(key["key"])  # Store it now: the raw key is never returned again
```

The response is a key object plus `key`. `hash` is the stable, log-safe ID for every other call.

```json
{
  "hash": "a1b2c3d4e5f6",
  "name": "customer-acme",
  "label": "mg_8Kx2pQr",
  "disabled": false,
  "limit": 50,
  "limit_reset": "monthly",
  "usage": 0,
  "limit_remaining": 50,
  "created_at": "2026-06-02T17:04:00Z",
  "project_id": null,
  "allowed_models": null,
  "routing_policy_id": null,
  "key": "mg_8Kx2pQrS..."
}
```

## Manage keys

| Endpoint                 | Does                                                                                                                                   |
| ------------------------ | -------------------------------------------------------------------------------------------------------------------------------------- |
| `GET /v1/keys`           | Lists keys newest first as `{object, data, has_more}`, at most 100 per page. Page with `offset` and `limit` while `has_more` is `true` |
| `GET /v1/keys/{hash}`    | Returns one key with its current usage                                                                                                 |
| `PATCH /v1/keys/{hash}`  | Updates `name`, `disabled`, `limit`, `limit_reset`, `allowed_models`, or `routing_policy_id`                                           |
| `DELETE /v1/keys/{hash}` | Revokes the key immediately and returns `204`. Its past spend stays in usage reporting                                                 |

On `PATCH`, omitted fields are left alone, `null` clears `allowed_models` or `routing_policy_id`, `"limit": null` leaves the limit in place, and `project_id` returns `400`. Pause a key with `"disabled": true`. Full schemas: [API reference](/merge-gateway/management-api).

## Restrict which models a key can call

`allowed_models` limits a key you hand to a team or customer to listed models.

**`cURL`**

```bash title="cURL"
curl -X PATCH https://api-gateway.merge.dev/v1/keys/a1b2c3d4e5f6 \
  -H "Authorization: Bearer $MERGE_GATEWAY_MANAGEMENT_KEY" \
  -H "Content-Type: application/json" \
  -d '{"allowed_models": ["openai/gpt-5.4-mini", "anthropic/claude-haiku-*"]}'
```

| Rule              | Detail                                                                                                        |
| ----------------- | ------------------------------------------------------------------------------------------------------------- |
| Entries           | Canonical `provider/model` IDs. A bare name such as `gpt-5.4-mini` returns `400`                              |
| Wildcards         | One trailing `*` matches a prefix, such as `anthropic/claude-opus-*`. A leading or interior `*` returns `400` |
| Aliases           | `@alias/` entries return `400`. List the models the policy routes to instead                                  |
| Size              | Up to 200 entries, duplicates collapsed                                                                       |
| `[]`              | Denies every model                                                                                            |
| Omitted or `null` | Unrestricted                                                                                                  |

Pinning a disallowed model returns `403 model_not_allowed`. Under a routing policy, the allowlist filters candidates instead, failing only when none is allowed. It applies on every endpoint, alongside your organization's [blocklist](/merge-gateway/security/per-customer-restrictions).

## Bind a routing policy to a key

`routing_policy_id` gives one key its own routing without a project or per-request field. Rebinding needs no rotation; `null` unbinds.

**`cURL`**

```bash title="cURL"
curl -X PATCH https://api-gateway.merge.dev/v1/keys/a1b2c3d4e5f6 \
  -H "Authorization: Bearer $MERGE_GATEWAY_MANAGEMENT_KEY" \
  -H "Content-Type: application/json" \
  -d '{"routing_policy_id": "3f0a9c1e-5b7d-4a2f-8c61-9d4e2b7a0f13"}'
```

A policy named on the request beats the binding, which beats customer, project, and organization defaults ([precedence](/merge-gateway/routing/using-policies#how-routing-policies-are-applied)). `POST /v1/embeddings` ignores bindings.

| Binding to                                                      | Result                                                                      |
| --------------------------------------------------------------- | --------------------------------------------------------------------------- |
| A policy in another organization                                | `404`                                                                       |
| An inactive policy                                              | `400`                                                                       |
| A policy scoped to a different project or customer than the key | `400`. Bind a policy scoped to the key's project, or an organization policy |

## Per-key spend limits

A key with `limit` and `limit_reset` returns `402 api_key_limit_exceeded` once the current window's spend reaches the limit. Windows reset at midnight UTC (weekly on Monday, monthly on the 1st). Each key object reports the window's `usage` and `limit_remaining` for showing a customer their balance. See how this combines with other scopes in [Budgets](/merge-gateway/cost/budgets).

## Per-key usage

**Usage & spend → By attribution → API key** lists each key's requests, tokens, and spend. Management API keys record no creating user, so give each a descriptive `name`, such as `customer-acme` or `alice@acme.com`.

## Next steps

#### [Budgets](/merge-gateway/cost/budgets)

How per-key limits combine with project and organization budgets

#### [Projects API](/merge-gateway/automation/projects-api)

Manage projects, their routing, and budgets from code

#### [Management API reference](/merge-gateway/management-api)

Full request and response schemas