> ## Documentation Index
> Fetch the complete documentation index at: https://developers.meshapi.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Admin Keys

> The mak_ credential a script uses to manage your organization — permissions, scopes, expiry, rotation, and revocation.

An admin key (`mak_...`) lets a script manage your organization — creating API keys, reading resolved limits, configuring alerts — without a person signing in. It is deliberately **not** the key you already have.

| | `rsk_...` | `mak_...` |
| - | - | - |
| Calls models, bills your balance | Yes | No |
| Manages keys, limits, alerts | No | Yes |
| Expiry | Lives until you suspend it | Always expires — up to 90 days |

The separation is enforced twice server-side, by credential prefix and by separate storage. A leaked inference key cannot mint more keys, and a leaked admin key cannot spend your balance.

<Note>
  Admin keys are minted from the dashboard by a signed-in person, in **Dashboard → Settings → Admin keys**. An admin key cannot mint another admin key, by design.
</Note>

***

## What an admin key can reach

A key carries an explicit permission set. Anything not granted is a `403`, and anything not in this table is out of reach for **every** admin key:

| Permission | Reaches |
| - | - |
| `keys:read` | `GET /v1/keys`, `GET /v1/keys/filter-options`, `GET /v1/keys/{id}/limits` |
| `keys:write` | `POST /v1/keys`, `PATCH /v1/keys/{id}`, `DELETE /v1/keys/{id}` |
| `limits:read` | `GET /v1/routing-policy-templates`, `GET /v1/routing-policy-templates/{id}` |
| `limits:write` | `PATCH` / `DELETE /v1/routing-policy-templates/{id}` |
| `alerts:read` | `GET /alerts`, and the webhook delivery log: `GET /v1/webhooks/deliveries`, `/stats`, `/{delivery_id}` |
| `alerts:write` | Alert channels and policies under `/alerts`, plus `POST /v1/webhooks/deliveries/test` and `/{delivery_id}/redeliver` |
| `org:read` | `GET /v1/orgs/current/permissions/catalogue` |

<Warning>
  Not reachable with an admin key, at any permission: **inference** (`/v1/chat/completions` and every other model call), **billing and payments**, **org governance** (invites, roles, teams, ownership), **usage and audit reads** — those take an `rsk_` key — and **minting or rotating admin keys**.

  Creating a routing-policy template is also dashboard-only; an admin key can read and edit templates but not create the first one.
</Warning>

## Scope

Scope decides *whose* rows a permission reaches:

| Scope | Reaches |
| - | - |
| `self` | Only rows the key itself owns |
| `team` | One team — `team_id` is required |
| `org` | The whole organization |

<Note>
  `alerts:*`, `limits:*`, and `org:read` act on the organization itself, so a narrower scope would select the same rows while reading as a restriction. They can only be granted at scope `org` — asking for them at `self` or `team` is a `422`. Only `keys:read` and `keys:write` are meaningful below org scope.
</Note>

You can grant only what you already hold: a permission you lack is a `403`, and so is a scope that reaches further than yours. A team admin can mint a `team`-scoped key for their own team, not for another.

***

## Creating one

Create one in **Dashboard → Settings → Admin keys**. The form maps onto `POST /v1/admin-keys`, which authenticates the signed-in person. No credential can mint an admin key, so this step is always a human one:

```json theme={null}
{
  "name": "ci-key-provisioner",
  "permissions": ["keys:read", "keys:write"],
  "scope": "org",
  "expires_at": "2026-11-01T00:00:00Z"
}
```

| Field | Notes |
| - | - |
| `name` | Required, non-blank. Shown in listings and on the audit trail |
| `permissions` | At least one, from the table above |
| `scope` | `self`, `team`, or `org` |
| `team_id` | Required when `scope` is `team`, and rejected otherwise |
| `expires_at` | **Required** — must carry a UTC offset, be in the future, and be at most **90 days** out. There is no default and no non-expiring admin key |

<Warning>
  The plaintext key is shown **once**, in the `key` field of the response. Every later read gives you `masked_key` (`mak_*****7f3a`) instead — only a hash is stored. Copy it straight into your secret store.
</Warning>

## Using it

The same `Authorization` header your API key uses:

```bash theme={null}
curl https://api.meshapi.ai/v1/keys \
  -H "Authorization: Bearer $MESH_ADMIN_KEY"
```

Only the management routes above accept it, and they accept nothing else on that plane.

***

## What the keys it creates belong to

An inference key minted by an admin key is owned by the **admin key's lineage**, not by a person. That has three consequences worth knowing before you automate anything:

* It lands in the admin key's own organization. Sending a different `org_id` is a `403`; sending a different `team_id` works only if the key's scope reaches that team, and a `self`-scoped key can only create in the org's default team.
* It bills the organization's shared balance, like any other org key — see [Organizations & Teams](/docs/getting-started/organizations).
* It survives rotation of the admin key that created it, because the lineage is what owns it. Replacing an admin key by minting a fresh one instead does **not** carry those keys over.

## Rotating

Rotate from **Dashboard → Settings → Admin keys** (`POST /v1/admin-keys/{key_id}/rotate`, optionally with `{"grace_hours": 24}`). Like minting, it is a signed-in action; a key cannot rotate itself.

Rotation mints a successor and shortens the predecessor to a grace window — **both keys work during the overlap**, which is what lets you redeploy without a gap. `grace_hours` defaults to 24 and is capped at 7 days.

The successor inherits the predecessor's name, permissions, scope, and team. It also inherits its **lifetime**, not the 90-day ceiling: rotating a deliberately short-lived key does not quietly turn it into a long-lived one. Pass `expires_at` to set a different one, subject to the same 90-day rule.

## Revoking

Revoke from **Dashboard → Settings → Admin keys** (`DELETE /v1/admin-keys/{key_id}`, optionally with a `reason`).

Revocation is **immediate and has no grace window** — that is the difference from rotation. `reason` is optional and is recorded on the key and in the audit trail.

## Listing

`GET /v1/admin-keys` and `GET /v1/admin-keys/{key_id}` return the masked key, permissions, scope, expiry, `last_used_at`, and who created it — never the plaintext. What you see depends on your own scope: org-wide access lists every key in the org, team access lists your team's keys plus your own, and anything narrower lists only the keys you created yourself.

***

## Errors

| Status | When |
| - | - |
| `401` | The key is revoked, expired, or unknown — a dead credential reads differently to an operator than a permissions failure |
| `403` | Permission not granted, scope too narrow, another organization, or no admin authority over the named team |
| `404` | The key exists but your scope cannot see it |
| `409` | Already revoked, or a stored grant that can no longer be reproduced on rotation — mint a replacement instead |
| `422` | `expires_at` missing an offset, in the past, or past 90 days; blank `name`; `team_id` without `scope: "team"`; an org-wide permission below org scope |

## Related

* [Authentication](/docs/getting-started/authentication) — `rsk_` vs `mak_`, and using a key to make requests
* [API Keys](/docs/getting-started/api-keys) — the management API this credential is for
* [Rate Limits & Spend Caps](/docs/getting-started/rate-limits) — resolving limits across tiers
* [Organizations & Teams](/docs/getting-started/organizations) — shared billing, teams, and what stays a dashboard action


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.