> This page is for Workforce.

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

> The Access Keys an admin creates in Workforce: production versus test, expiry and restrictions, rotation, and minting narrower keys through the API. Also how these differ from the keys employees get.

An Access Key authenticates your organization's own automation against the Merge API and the MCP endpoint. Every call carries one in the `Authorization` header:

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

The word `Bearer` is required and case-sensitive. The key never appears anywhere else.

Employees are never handed one of these. Three other credentials are theirs instead: a Gateway key minted per synced Group and revealed once on their **Keys** tab, a device credential minted per machine by the [desktop client](/merge-workforce/devices/overview) or the [Workforce CLI](/merge-workforce/rollout/workforce-cli), and a personal access token they generate themselves for MCP clients that cannot complete an OAuth flow. Only employees provisioned through SCIM can hold that last one, and each holds at most one at a time.

## Production and test

|               | Production                                                 | Test                                                   |
| ------------- | ---------------------------------------------------------- | ------------------------------------------------------ |
| **How many**  | Any number, plus a master key if your organization has one | Any number                                             |
| **Scope**     | Real employees, real Connector calls, production audit log | Sandbox: test employees, test data, separate audit log |
| **Rotatable** | Regenerate the secret in place, or revoke and replace      | Created and deleted at will, with no regenerate        |
| **Use for**   | Live automation                                            | Development, staging, CI                               |

Test keys give you a clean sandbox. Credentials and tool calls made with a test key are isolated from production. The trade-off: a test key cannot read production data and a production key cannot read test resources, so mixing them up shows up as `404 not found` on resources you are certain exist.

Manage both at [Settings → API keys](https://wf-develop.merge.dev/settings/api-keys). Creating, rotating, and revoking requires the **Manage organization API keys** permission.

## Generating keys

**Master key.** An organization's one unnamed production key carries a **master** badge, reads as **Full access**, and has no **Revoke key** option. Click **Regenerate secret** to rotate it. Rotation invalidates the old value immediately, so propagate the new one everywhere before clicking, or you have an outage as long as it takes to redeploy. Not every organization has one, so an empty production list is normal until you add a key.

**Additional production keys.** Click **Add key** and name it. Hold as many as you need, so each service or environment carries its own credential. Each key's overflow menu offers **Copy key ID**, **Regenerate secret**, and **Revoke key**, and the row summarizes its scope as its Tool Pack and employee restrictions.

**Test keys.** Same flow, named for what they are for: `ci`, `alice-dev`, `staging-2`.

Every key's value is shown once at creation, production and test alike. Lose it and the only path is a replacement.

## Expiry and restrictions

A key can carry an optional expiry date, set when you create it. The key stops working on that date, which suits a contractor's access or a time-boxed migration. Production and test keys share one create dialog, so both take an expiry.

A key can also be restricted to specific Tool Packs, specific employees, or both, from the same dialog. Three things to know before you rely on it:

* Tool Pack and employee restrictions are enforced on every tool call. A key restricted to one employee cannot call tools as another: the out-of-scope call returns `404` with "Resource not in API key scope.", never that employee's data. It is a `404` rather than a `403` on purpose, so a restricted key cannot tell an out-of-scope employee apart from one that does not exist
* The dashboard does not let you choose scopes, so a key restricted here still reaches the management endpoints and can read and write configuration. To withhold that too, mint the key through the API with `["runtime:all"]` and leave `management:all` off
* A key's restrictions cannot be edited after creation. To change them, revoke the key and create a replacement

## Rotation

Rotate production keys at least once a year, and immediately if you suspect one leaked.

The cleanest rotation adds a key rather than replacing one in place, so there is never a window with no valid credential deployed:

1. Create a new production key and copy its value.
2. Roll it out everywhere the old one is used: secret manager update, release, confirm.
3. Confirm traffic on the new key.
4. Revoke the old key.

Step 4 has one guard: you cannot revoke your organization's last unrestricted production key, because that would leave nothing able to manage the others. Create the replacement first and the revoke goes through. The master key cannot be revoked at all, so rotating that one means regenerating it in place: roll the replacement out first, then click **Regenerate secret**, since the old value stops working the moment you do.

If a key has leaked publicly, by being committed to a repo or posted in a chat, revoke or regenerate first and roll out second. An outage costs less than someone else holding your key.

## Minting keys through the API

Everything on the API keys page has an equivalent under `/api/v1/access-keys/`, which is how you mint short-lived keys per deployment instead of sharing one long-lived key.

A key you create through the API can be narrower than the key that created it, never wider. `POST /api/v1/access-keys/` accepts `tool_pack_ids`, `registered_user_ids`, `scopes`, and either `expires_at` or `expires_in`, and each has to be a subset of the calling key's own grant. A key limited to one Tool Pack cannot mint a key that reaches a second one.

Unlike the dashboard, the API expires keys by default: omit both expiry fields and the key expires 90 days out. Send `"expires_at": null` for one that never expires.

The two scopes are `runtime:all` for MCP tool calls and `management:all` for the resource-management endpoints. Scopes are the one thing the dashboard cannot set, so a key that should run tools without reading or changing configuration has to be minted here. Passing `null` for `tool_pack_ids` or `registered_user_ids` means unrestricted on that dimension, and it is accepted only when the calling key is itself unrestricted there.

```bash
curl -X POST https://ah-api.merge.dev/api/v1/access-keys/ \
  -H "Authorization: Bearer $MERGE_AH_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "nightly-reconcile",
    "tool_pack_ids": ["1d8b6f4a-1b3e-4f9b-9d3a-3a8e8b9c0d1f"],
    "scopes": ["runtime:all"],
    "expires_in": 3600
  }'
```

The `key` value comes back once, on the create response. After that only `key_masked` is readable.

The same subset rule governs `POST /api/v1/access-keys/{key_id}/regenerate/` and `POST /api/v1/access-keys/{key_id}/revoke/`. A key can act on itself, or on a key whose grant it fully covers:

| Dimension   | The rule                                                 |
| ----------- | -------------------------------------------------------- |
| Environment | Both keys are production, or both are test               |
| Scopes      | The target's scopes are a subset of the acting key's     |
| Expiry      | The target expires no later than the acting key          |
| Tool Packs  | The target's Tool Packs are a subset of the acting key's |
| Employees   | The target's employees are a subset of the acting key's  |

Anything else returns `403` with "Access key may only act on itself or on keys within its scope." The response does not name the pair, so compare the two keys' grants yourself when a rotation is refused.

Coverage rather than lineage is what makes this usable in both directions. Every key minted through the API is covered by the key that minted it, so a service can always rotate its own children, and keys created in the dashboard are covered by your unrestricted production key.

The expiry clause is the part to plan around: a one-hour scoped key cannot rotate your never-expiring production key, which is what stops a short-lived leak from becoming a permanent one. If a scoped service needs to rotate a long-lived key, give the service a long-lived key of its own rather than widening the scoped one.

## Gateway keys are separate

Access Keys reach tools. Model traffic runs on Gateway keys, and the two are minted in different places.

Employee and Group Gateway keys are issued automatically on sync, so there is nothing to hand out. Rotate one from the employee's **Keys** tab: rotation revokes and re-mints in a single step, and the replacement reveals once.

For automation that provisions Gateway keys itself, a management key is the credential to use. It creates, caps, and revokes Gateway keys but cannot call a model, which keeps the credential that provisions keys separate from the ones that spend money. Per-key spend limits and model allowlists are set the same way. See [Management API keys](/merge-gateway/automation/api-keys).

## Storage

Treat every key like any other secret.

* **Do not commit them.** A gitignored `.env` locally, a secret manager in deployed environments
* **Do not expose them client-side.** A production Access Key acts on behalf of your employees, so calls belong on a server you control
* **Do not share them across environments or services.** Each environment, and ideally each service, holds its own

The [Audit trail](/merge-workforce/logs/audit-trail) records every key creation, regeneration, and revocation. When one key rotates another through the API, the entry names both the acting and the target key, so a rotation chain reads without correlating requests by hand.

## Next

Bring your own OAuth app per Connector with [Application credentials](/merge-workforce/connectors/application-credentials).