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.
#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 | Access token starting wm_at_ | Any |
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.
#Getting a key
- Sign in to app.warmerly.com.
- Go to Settings > Platform API keys. On a plan below Agency this page shows an upgrade prompt instead of the form.
- 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.
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 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 own workspace (your oldest active one), because an API key has no browser selection to fall back on. A workspace you are not a member of is ignored the same way. If you work across several workspaces, send it explicitly on every workspace-scoped call. |
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, /alerts |
| Workspace | X-Workspace-Id, else your own workspace | /projects, /suppression, /usage/quotas, /billing/me, /accounts/limits, /warmup/stats, /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 for access. The Agency plan check still follows the headers you send (see below), so for a campaign in a workspace other than your own, send X-Project-Id or X-Workspace-Id too. /accounts/{id}/… endpoints work the same way.
#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 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:
{ "error": { "code": "too_many_requests", "message": "rate_limit_exceeded", "details": null } }This per-key 429 carries 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), and the approval screen is explained in How do I connect, check or revoke an AI app?.
#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}. 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:
- Create a new key and name it after the integration it replaces.
- Deploy the new key to your application or secret store.
- Check the old key's
lastUsedAtinGET /keys, or the last-used time on the settings page. When it stops advancing, nothing is using it any more. - 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?.
#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 not plan-gated, so it still works from the dashboard after a downgrade. A revoke sent with the key itself is refused with
403 agency_plan_requiredwhile the workspace is off Agency.
#Errors
An invalid or revoked key returns 401:
{ "error": { "code": "unauthorized", "message": "unauthorized", "details": null } }A missing or malformed X-Project-Id on a project-scoped endpoint returns 400:
{ "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.
#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.