<!-- Authentication — https://docs.warmerly.com/authentication -->

# Authentication

Every request to the Warmerly API is authenticated with an API key, sent as the
`X-Api-Key` header.

**API access is an Agency-plan feature.** Without Agency, creating or listing keys, and any
request made with a key, returns `403 agency_plan_required`. Upgrade at
[warmerly.com/pricing](https://warmerly.com/pricing).

## Ways to authenticate

| Method | Used by | Credential | Plan |
| --- | --- | --- | --- |
| API key | Your own code, scripts and automation tools | `X-Api-Key: wmv_...` header | Agency |
| Signed-in session | The Warmerly dashboard in your browser | Session cookie | Any |
| Mobile session | The Warmerly Android app | `Authorization: Bearer <session token>` | Any |
| OAuth connection | AI assistants such as Claude, ChatGPT and Cursor, through the [MCP server](https://docs.warmerly.com/ai) | Access token starting `wm_at_` | Any |
| Transactional sending key | The [transactional email](https://docs.warmerly.com/transactional) API | `Authorization: Bearer wm_tx_...` or `X-Api-Key: wm_tx_...` | Transactional plan |

For your own integrations use an API key. The session cookie and mobile token belong to a
signed-in person and are not meant for scripts. The two key types are not interchangeable:
`wmv_` keys are refused by the transactional send API, and `wm_tx_` keys are refused everywhere
else.

## Getting a key

1. Sign in to [app.warmerly.com](https://app.warmerly.com/login).
2. Go to **Settings > Platform API keys**. On a plan below Agency this page shows an upgrade
   prompt instead of the form.
3. Enter a **Key name**, click **Create key**, and copy the secret shown. It is only displayed
   once and cannot be retrieved again. If you lose it, revoke the key and create a
   new one.

Keys are prefixed `wmv_` and are scoped to your user account (they can access every
workspace/project you're a member of, subject to the project you select via
`X-Project-Id`). You can also create keys programmatically, see [API Keys](https://docs.warmerly.com/keys).

A key carries your full access. There are no per-endpoint scopes and no read-only keys, so a key
can do whatever you can do in the workspaces you belong to. Warmerly stores only a SHA-256 hash
of the key, which is why the secret cannot be shown again. Each user can hold up to 10 active
keys.

## Required headers

| Header | Required | Description |
| --- | --- | --- |
| `X-Api-Key` | Always | Your API key secret. |
| `X-Project-Id` | Most endpoints | The UUID of the project to operate on (mailboxes, campaigns, and inbox data all belong to a project). Find it in the dashboard URL or via the [Workspaces & Projects](https://docs.warmerly.com/workspaces) endpoints. |
| `X-Workspace-Id` | Optional | Which workspace a **workspace-scoped** endpoint (suppression, usage, billing, channel limits, inbox-wide reads) should act in. Omit it and the call uses your account's currently active workspace, which for a key that never touches the dashboard means the one selected there, not necessarily the one you meant. If you work across several workspaces, send it explicitly on every workspace-scoped call. |

```bash
curl https://app.warmerly.com/api/v1/accounts \
  -H "X-Api-Key: wmv_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" \
  -H "X-Project-Id: 8f14e45f-ceea-4c6a-8c8d-6b1a5b5c9c1a"
```

Endpoints that aren't project-scoped (e.g. listing your workspaces, managing API
keys) only need `X-Api-Key`.

There are three scopes, and mixing them up is the most common integration mistake:

| Scope | Identified by | Examples |
| --- | --- | --- |
| Account | the key itself | `/keys`, `/workspaces`, `/projects`, `/alerts` |
| Workspace | `X-Workspace-Id`, else your active workspace | `/suppression`, `/usage/quotas`, `/billing/me`, `/accounts/limits`, `/inbox/*` |
| Project | `X-Project-Id` (required) | `/accounts`, `/campaigns`, `/email-finder/*` |

Campaign-scoped endpoints are the exception that needs neither header: `/campaigns/{id}/…`
resolves access from the campaign id in the URL, so passing a mismatched `X-Project-Id`
changes nothing.

## Which plan is checked

A key has no workspace of its own, so Warmerly checks the plan of the workspace the request acts
on: the project in `X-Project-Id` if you send one and you are a member of its workspace, otherwise
the workspace in `X-Workspace-Id`, otherwise your own workspace. Naming a workspace never grants
anything. You can only use the plan of a workspace you already belong to.

If that workspace is not on Agency (for example after a downgrade), requests return
`403 agency_plan_required`. The key is not revoked: it works again after you upgrade. The plan
check is cached for 60 seconds, so a downgrade or an upgrade can take up to a minute to show.

A suspended account gets `403 account_suspended` on every request, including ones made with a
key.

## Rate limits

Each key has its own per-minute rate limit (default 60 requests/minute, configurable
per key up to 120). Exceeding it returns `429 too_many_requests`. A few endpoints layer
an additional, tighter limit on top of this. See [Errors & Rate Limits](https://docs.warmerly.com/errors) for the
full picture, including the exact response shape and per-endpoint limits.

The per-key limit is a fixed window per calendar minute (UTC). Each request counts toward the
current minute, and once the count passes the key's limit every further request in that minute
returns:

```json
{ "error": { "code": "too_many_requests", "message": "rate_limit_exceeded", "details": null } }
```

Responses carry no `Retry-After` header, so wait for the next minute to begin before retrying,
and spread bulk jobs out instead of sending them in a burst. You choose a key's limit when you
create it with `rateLimitPerMin` (1 to 120, default 60); the dashboard form uses the default.
Running out of a monthly allowance (verifications, lead exports, lookups) is a different failure
and returns `402`, not `429`.

## Authenticating AI assistants (MCP)

AI assistants do not use API keys. The MCP server at `https://app.warmerly.com/api/mcp` uses
OAuth 2.1 with PKCE (S256 only), so you approve a connection once on a consent screen and never
copy a secret. Each connection is scoped to one signed-in user's one workspace, and it is
available on every plan including Free.

- Access tokens last 1 hour. Refresh tokens last 30 days and rotate each time they are used.
- Tool calls are limited to 60 per minute per connection, and tools that send email to a real
  person have a tighter limit.
- To see or cut off a connection, open **Settings > Connected apps** and revoke it. Removing a
  member from a workspace also ends their connections to it.

Setup steps for each assistant are in [Use Warmerly with AI (MCP)](https://docs.warmerly.com/ai), and the approval screen
is explained in [How do I connect, check or revoke an AI app?](https://docs.warmerly.com/help/manage-connected-ai-apps).

## Revoking a key

Revoke a key any time from **Settings > Platform API keys** with the **Revoke** button on its
row, or with [`DELETE /keys/{id}`](https://docs.warmerly.com/keys#revoke-a-key). Revoked keys fail every request
immediately with `401 unauthorized`, there is no grace period.

## Rotating a key

Rotate keys on a schedule, and immediately if one may have leaked. Because you can hold 10 active
keys, rotation needs no downtime:

1. Create a new key and name it after the integration it replaces.
2. Deploy the new key to your application or secret store.
3. Check the old key's `lastUsedAt` in [`GET /keys`](https://docs.warmerly.com/keys#list-keys), or the last-used time on
   the settings page. When it stops advancing, nothing is using it any more.
4. Revoke the old key. Revocation is immediate and cannot be undone.

Key creation and revocation are recorded in the workspace audit log as `api_key.created` and
`api_key.revoked`. See [What is the audit log?](https://docs.warmerly.com/help/audit-log).

## Keeping keys safe

- Keep keys in environment variables or a secrets manager, never in source control, front-end
  code or a mobile app. Anyone holding a key can act as you.
- Use one key per integration, named after it, so you can revoke one without breaking the others.
- Call the API from a server, not from a browser page, where visitors could read the key.
- If a key leaks, revoke it first and investigate second.
- Revoke keys you no longer use. Revoking is always allowed, even after a downgrade.

## Errors

An invalid or revoked key returns `401`:

```json
{ "error": { "code": "unauthorized", "message": "unauthorized", "details": null } }
```

A missing or malformed `X-Project-Id` on a project-scoped endpoint returns `400`:

```json
{ "error": { "code": "bad_request", "message": "missing X-Project-Id header", "details": null } }
```

| Status | `error.code` | Cause |
| --- | --- | --- |
| `401` | `unauthorized` | Key missing, unknown or revoked. |
| `403` | `agency_plan_required` | The workspace the request acts on is not on Agency. |
| `403` | `account_suspended` | The account is suspended. |
| `400` | `bad_request` | `X-Project-Id` missing or not a valid UUID. |
| `429` | `too_many_requests` | The key's per-minute limit was exceeded. |

The full list is in [Errors & Rate Limits](https://docs.warmerly.com/errors).

## Common questions

**Why do I get `403 agency_plan_required` with a valid key?** The workspace the request acts on
is not on the Agency plan. Check the `X-Project-Id` and `X-Workspace-Id` you are sending.

**Can I see my key again?** No. Revoke it and create a new one.

**Can I limit a key to certain endpoints?** No. Keys have no scopes and carry your access.

**Does one key work across workspaces?** Yes, for every workspace you are a member of, as long as
the workspace you target is on Agency. Send `X-Project-Id` or `X-Workspace-Id` to choose.

**How is this different from the transactional sending keys?** Sending keys (`wm_tx_`) are for the
transactional email API and belong to one workspace. See [Transactional email](https://docs.warmerly.com/transactional).
