---
title: Authentication
description: API keys, scopes, headers, and how agent runtimes authenticate to MCP.
---

# Authentication

Authenticate REST calls with an API key in the `X-API-Key` header;
MCP agent runtimes use a bearer JWT. The two credential types sit over
one identity surface:

| Surface | Credential | Use case |
|---|---|---|
| **REST** | API key in `X-API-Key` | Server-to-server integrations, scripts, CI |
| **MCP** | API key **or** session JWT, as `Authorization: Bearer` | Agent runtimes — Claude Code, Cursor, custom |
| **Dashboard** | Session JWT + refresh + MFA | Humans in the browser |

> API keys never grant admin-plane access. Billing, member invites,
> the credential vault, SSO/SCIM, and tenant settings stay JWT-only. A
> leaked key has a bounded blast radius — its minted scope set is the
> maximum reach.

---

## API keys

### Minting a key

From the dashboard:

1. Sign in.
2. **Settings → API keys → New key.**
3. Name it (e.g. `production-erp-sync`), pick scopes, copy the plaintext.
4. The plaintext is shown **once**. We store a one-way Argon2 hash and
   cannot recover it. If you lose it, revoke and re-mint.

Keys can also be minted programmatically with the `api-keys` API using a
**user session JWT** (an owner or admin whose email is verified) — the
plaintext is returned **once**. Unknown scopes are dropped against the
catalog below; if *every* requested scope is unknown the call returns
`400 bad_request`. API keys themselves cannot mint other keys — key
management requires a user JWT, so a compromised key can never create a
successor with broader scope.

### Format

```
ak_live_dXt8q2Rb_9f3c…6Pk
└┬┘ └┬─┘ └──┬───┘ └───┬───┘
 │   │     │          └─ secret (shown once, never stored in plaintext)
 │   │     └──────────── public prefix (identifies the key in logs / lists)
 │   └────────────────── mode: "live" or "test"
 └────────────────────── key marker
```

### Sending a key

API keys go in the `X-API-Key` header:

```http
X-API-Key: ak_live_…
```

> `Authorization: Bearer …` is reserved for **user/MCP JWTs** and is not
> a valid transport for an API key. Put API keys in `X-API-Key`.

If your key is scoped to a tenant with more than one organization, add
`X-Org-Slug: <slug>` to pin the org context.

### Test vs live keys

| Prefix | Cost | Notes |
|---|---|---|
| `ak_test_…` | €0 | Sandbox keys. Refused in the production environment. |
| `ak_live_…` | Billed | Real traffic. |

Mint test keys for CI; mint live keys for production deployments.

### Rotating a key

Rotate a key from the dashboard (or the `api-keys` API). Rotation keeps the
name, scopes, and `expires_at` intact and issues a fresh secret, returned
with its plaintext exactly once. Deploy the new key **first**, then rotate —
the old secret stops working immediately.

### Revoking a key

Revoke a key from the dashboard. Revocation is immediate — requests with the
revoked key get `401` (`invalid_api_key`) from the next request onward.

### Listing keys and their scopes

View your keys in the dashboard. The list shows each active key's `prefix`,
`name`, `scopes`, and `expires_at` — never the plaintext. Key management is
a user-session (owner/admin) operation, so it isn't reachable with an API
key.

---

## Scopes

Scopes follow `<resource>:<action>`. Mint the **narrowest** set you can:
a leaked `parts:read` key cannot trigger a billable calculation, and a
leaked `wallet:read` key cannot move money.

| Scope | Lets the holder |
|---|---|
| `parts:read` | List + read parts, revisions, **and calculation results** |
| `parts:write` | Create / update / delete parts and revisions, **and create + run calculations** (billable) |
| `uploads:read` | List + read existing uploads |
| `uploads:write` | Upload new CAD / drawings / RFQ text |
| `webhooks:read` | List webhook endpoints and deliveries |
| `webhooks:write` | Create / update / delete webhook endpoints |
| `wallet:read` | Read wallet balance and usage |
| `audit:read` | Export the audit log (SIEM integration) |

> **Calculations authorize on `parts:read` / `parts:write`.** Finer-
> grained `parts:calculations:read` / `parts:calculations:write` scopes
> exist in the catalog for forward compatibility, but calculation
> endpoints currently enforce `parts:read` (reads) and `parts:write`
> (create + run). To run a calculation, mint a key with `parts:write`.

> **Admin scopes don't exist.** Plan changes, member invites, SSO/SCIM,
> credentials, and OAuth provider config are human-dashboard-only.
> Machine keys cannot reach those surfaces by design.

---

## Required headers

Every authenticated REST request includes:

```http
X-API-Key:    ak_live_…
Content-Type: application/json    (or multipart/form-data for uploads)
```

Recommended:

```http
Idempotency-Key:  9c5f-… (UUIDv4 from your client — see Idempotency)
X-Org-Slug:       your-org-slug   (required only for multi-org tenants)
```

We stamp `X-API-Version` and `X-Request-ID` on **every response**. Echo
the request id when filing a support ticket — we can pull the
structured log row in one query.

---

## MCP authentication

The MCP server at `https://api.arcnm.io/mcp-server/mcp` is a bearer-token
protected resource and publishes
[RFC 9728 protected-resource metadata](https://datatracker.ietf.org/doc/html/rfc9728)
at `/.well-known/oauth-protected-resource`. The verifier accepts **either**
credential in the `Authorization: Bearer …` header:

- an ARCNM **API key** (`ak_live_…` / `ak_test_…`) — the same key you use
  for REST, just carried as a bearer token (tried first); or
- a tenant-scoped **session access token** (a JWT, audience-pinned to
  `/api/v1`). Refresh tokens and tokens for other audiences are rejected.

Most agent setups use an API key — it's long-lived, revocable, and
scoped. Supply it to your agent runtime's client config — see
[MCP](./mcp.md) for the exact `claude mcp add` / Cursor `mcp.json`
snippets. Note the MCP door only reads the `Authorization: Bearer`
header — there is no `X-API-Key` transport on the MCP server (that header
is REST-only).

---

## Verifying webhook payloads server-side

ARCNM signs every webhook with the per-endpoint secret using the
[Standard Webhooks](https://www.standardwebhooks.com/) scheme. Verify
the `webhook-signature` header before trusting a delivery — see
[Webhooks → verifying signatures](./webhooks.md#verifying-signatures).

---

## See also

- [Errors](./errors.md) — `401 invalid_api_key`, `403 insufficient_scope`.
- [Rate limits](./rate-limits.md) — per-key and per-caller limits.
- [Versioning](./versioning.md) — the `X-API-Version` response header.
- [MCP](./mcp.md) — bearer auth, tenant scoping, exposed tools.
