---
title: Endpoint scope map
description: Every endpoint, which auth types it accepts, and which permission it requires.
icon: Route
---

This page is the source of truth for what an API key can and cannot reach. Every route in the backend falls into one of three buckets:

- **API key accepted**: works with both a JWT (dashboard user) and an API key with the listed permission.
- **JWT only**: never reachable via an API key. Used for billing, governance, websocket bootstrap, danger-zone destruction, the OAuth onboarding flow, and admin tools.
- **Public**: no auth required (webhooks with signature verification, health check, OAuth bouncer pages).

When an endpoint says "JWT permission: X / API permission: Y", the dual-auth middleware checks the relevant one based on which credential the caller used.

All paths below are relative to the versioned base URL `https://api.warmbly.com/v1` (for example `/campaigns` is `https://api.warmbly.com/v1/campaigns`). See [versioning](/api/) for details.

## API key accepted

### Emails

| Method | Path | API Permission |
|--------|------|----------------|
| GET | `/emails` | `READ_EMAILS` |
| GET | `/emails/:id` | `READ_EMAILS` |
| PATCH | `/emails/:id` | `WRITE_EMAILS` |
| PATCH | `/emails/:id/track` | `WRITE_EMAILS` |
| DELETE | `/emails/:id` | `WRITE_EMAILS` |
| POST | `/emails/:id/send` | `SEND_CAMPAIGNS` |

### Campaigns and sequences

| Method | Path | API Permission |
|--------|------|----------------|
| GET | `/campaigns` | `READ_CAMPAIGNS` |
| POST | `/campaigns` | `WRITE_CAMPAIGNS` |
| GET | `/campaigns/:id` | `READ_CAMPAIGNS` |
| PATCH | `/campaigns/:id` | `WRITE_CAMPAIGNS` |
| DELETE | `/campaigns/:id` | `WRITE_CAMPAIGNS` |
| GET | `/campaigns/:id/advanced` | `READ_CAMPAIGNS` |
| PATCH | `/campaigns/:id/advanced` | `WRITE_CAMPAIGNS` |
| GET | `/campaigns/:id/ab-variants` | `READ_CAMPAIGNS` |
| POST | `/campaigns/:id/ab-variants` | `WRITE_CAMPAIGNS` |
| PATCH | `/campaigns/:id/ab-variants/:variantId` | `WRITE_CAMPAIGNS` |
| DELETE | `/campaigns/:id/ab-variants/:variantId` | `WRITE_CAMPAIGNS` |
| GET | `/campaigns/:id/ab-analysis` | `READ_ANALYTICS` |
| POST | `/campaigns/:id/preflight` | `SEND_CAMPAIGNS` |
| POST | `/campaigns/:id/test-email` | `SEND_CAMPAIGNS` |
| POST | `/campaigns/:id/start` | `SEND_CAMPAIGNS` |
| POST | `/campaigns/:id/stop` | `SEND_CAMPAIGNS` |
| GET | `/campaigns/:id/logs` | `READ_CAMPAIGNS` |
| GET/POST/PATCH/DELETE | `/campaigns/:id/steps[/:sid]` | `READ_CAMPAIGNS` for GET, `WRITE_CAMPAIGNS` otherwise |

### Contacts

| Method | Path | API Permission |
|--------|------|----------------|
| POST | `/contacts/search` | `READ_CONTACTS` |
| POST | `/contacts` | `WRITE_CONTACTS` |
| DELETE | `/contacts` | `BULK_CONTACTS` |
| PATCH | `/contacts` | `BULK_CONTACTS` |
| PATCH | `/contacts/:id` | `WRITE_CONTACTS` |
| DELETE | `/contacts/:id` | `WRITE_CONTACTS` |
| GET | `/contacts/:id/notes` | `READ_CONTACTS` |
| POST | `/contacts/:id/notes` | `WRITE_CONTACTS` |
| PATCH | `/contacts/:id/notes/:noteId` | `WRITE_CONTACTS` |
| DELETE | `/contacts/:id/notes/:noteId` | `WRITE_CONTACTS` |
| GET | `/contacts/:id/activities` | `READ_CONTACTS` |
| GET | `/contacts/:id/deals` | `READ_CRM` |

### Unibox

| Method | Path | API Permission |
|--------|------|----------------|
| GET | `/unibox` | `READ_UNIBOX` |
| GET | `/unibox/count` | `READ_UNIBOX` |
| GET | `/unibox/thread` | `READ_UNIBOX` |
| GET | `/unibox/:id` | `READ_UNIBOX` |
| PATCH | `/unibox/seen` | `WRITE_UNIBOX` |
| POST | `/unibox/reply` | `WRITE_UNIBOX` |

### Templates and CRM

| Method | Path | API Permission |
|--------|------|----------------|
| GET | `/templates`, `/templates/:id` | `READ_TEMPLATES` |
| POST/PATCH/DELETE | `/templates[/:id]` | `WRITE_TEMPLATES` |
| GET | `/crm/pipelines`, `/crm/pipelines/:id` | `READ_CRM` |
| POST/PATCH/DELETE | `/crm/pipelines[/:id][/stages[/:stageId]]` | `WRITE_CRM` |
| GET | `/crm/deals`, `/crm/deals/:id` | `READ_CRM` |
| POST/PATCH/DELETE | `/crm/deals[/:id]` | `WRITE_CRM` |
| GET | `/crm/tasks`, `/crm/tasks/:id` | `READ_CRM` |
| POST/PATCH/DELETE | `/crm/tasks[/:id]` | `WRITE_CRM` |

### Analytics and audit

| Method | Path | API Permission |
|--------|------|----------------|
| GET | `/analytics/*` (dashboard, deliverability, warmup, campaigns, accounts, usage) | `READ_ANALYTICS` |
| GET | `/audit-logs` | `READ_AUDIT_LOGS` |

### API key self-service

| Method | Path | API Permission |
|--------|------|----------------|
| GET | `/api-keys` | `API_KEYS` |
| POST | `/api-keys` | `API_KEYS` |
| GET | `/api-keys/permissions` | `API_KEYS` |
| GET | `/api-keys/:id` | `API_KEYS` |
| PATCH | `/api-keys/:id` | `API_KEYS` |
| DELETE | `/api-keys/:id` | `API_KEYS` |

### OAuth apps

Registering and managing the OAuth apps your workspace owns. The flow itself (authorize, token, revoke) is listed under JWT only and Public below.

| Method | Path | API Permission |
|--------|------|----------------|
| GET | `/oauth/applications` | `API_KEYS` |
| POST | `/oauth/applications` | `API_KEYS` |
| GET | `/oauth/applications/:id` | `API_KEYS` |
| PATCH | `/oauth/applications/:id` | `API_KEYS` |
| DELETE | `/oauth/applications/:id` | `API_KEYS` |
| POST | `/oauth/applications/:id/rotate-secret` | `API_KEYS` |
| GET | `/oauth/applications/:id/webhook-secret` | `API_KEYS` |
| POST | `/oauth/applications/:id/webhook-secret/rotate` | `API_KEYS` |
| GET | `/oauth/applications/:id/webhook-endpoints` | `API_KEYS` |
| GET | `/oauth/applications/:id/webhook-deliveries` | `API_KEYS` |

### Operations

| Method | Path | API Permission |
|--------|------|----------------|
| GET/PATCH | `/outreach/settings` | `WRITE_CAMPAIGNS` |
| POST | `/deliverability/events` | `WRITE_CAMPAIGNS` |
| GET | `/tasks/dlq` | `SEND_CAMPAIGNS` |
| POST | `/tasks/dlq/:id/replay` | `SEND_CAMPAIGNS` |
| GET/POST/PATCH/DELETE | `/webhooks[/:id]` | `WEBHOOKS` |
| POST | `/webhooks/:id/rotate-secret` | `WEBHOOKS` |
| POST | `/webhooks/:id/verify` | `WEBHOOKS` |
| GET | `/webhooks/event-types` | `WEBHOOKS` |
| GET | `/webhooks/deliveries` | `WEBHOOKS` |
| GET | `/webhooks/:id/deliveries` | `WEBHOOKS` |
| POST | `/webhooks/deliveries/:deliveryId/redeliver` | `WEBHOOKS` |
| GET | `/webhooks/throttle-drops` | `WEBHOOKS` |
| GET/POST/DELETE | `/integrations/*` | `INTEGRATIONS` |
| GET/POST/PATCH/DELETE | `/warmup/routing[/:id]` | `WARMUP_ROUTING` |

### Retry safety

Mutating API requests may include an `Idempotency-Key` header. Warmbly stores the completed response for 24 hours per organization and key. Reusing the same key with the same method, route, query, and body replays the original response with `X-Idempotent-Replayed: true`; reusing the key with a different request returns `409 Conflict`.

### Identity

| Method | Path | API Permission |
|--------|------|----------------|
| GET | `/me` | (any authenticated key) |

`GET /me` returns who the active credential belongs to: `user_id`, `email`, `name`, `organization_id`, `organization_name`, `auth_type` (`api_key`, `oauth`, or `jwt`), and the granted `scopes`. It requires no specific permission, so it is the right call for validating a connection and rendering a human-readable label. Unlike `/auth/me` (JWT only), it is reachable by API keys and OAuth tokens.

### Reference data

| Method | Path | API Permission |
|--------|------|----------------|
| GET | `/plans` | (any authenticated key) |
| GET | `/timezones` | (any authenticated key) |

## JWT only

These never accept an API key. They depend on a human-bound session: billing flows, governance, OAuth onboarding, websocket bootstrap, and operational destruction.

- `POST /auth/login`, `/auth/login/confirm`, `/auth/register`, `/auth/register/confirm`, `/auth/refresh`, `/auth/reset-password`, `/auth/reset-password/confirm`
- `POST /auth/logout`, `POST /auth/logout-all`, `GET /auth/me`, `PATCH /auth/me/onboarding`
- `POST /auth/me/avatar`, `DELETE /auth/me/avatar`
- `POST /emails/onboarding/oauth/start`, `POST /emails/onboarding/oauth/finish`, `POST /emails/onboarding/smtp-imap`
- `GET /oauth/authorize/details`, `POST /oauth/authorize` (the consent flow: a human approves a third-party app)
- `GET /oauth/authorized-apps`, `DELETE /oauth/authorized-apps/:id` (apps the user has authorized)
- `POST /getaway` (websocket bootstrap)
- `GET /realtime/info`
- `GET /me/danger-zone`, `POST /me/danger-zone/delete`, `DELETE /me/danger-zone/delete`
- `GET /invitations`, `POST /invitations/accept`
- All of `/organization/*` (create, switch, members, invitations, transfer ownership, avatar, danger zone)
- All of `/subscription/*` (checkout, portal, cancel, change-plan, preview-change, enterprise-inquiry, discounts, referrals, etc.)
- All of `/admin/*`

### Referrals and discounts

The referral program and billing discount history are part of `/subscription/*`, so they are JWT only and never accept an API key. There is no API permission scope for them; browser callers are gated by the org permission below. The discount validation and checkout endpoints are listed under [Subscription and billing](/api/account-and-organization/) in the reference.

| Method | Path | JWT permission |
|--------|------|----------------|
| GET | `/subscription/referral` | `manage_billing` |
| POST | `/subscription/referral` | `manage_billing` |
| GET | `/subscription/referral/attributions` | `manage_billing` |
| GET | `/subscription/referral/earnings` | `manage_billing` |
| GET | `/subscription/discounts` | `manage_billing` |

## Public

- `GET /health`
- `POST /webhooks/github/releases` (HMAC-SHA256 signature)
- `POST /webhook/stripe` (Stripe signature)
- `POST /webhook/campaign`, `/webhook/email`, `/webhook/user-email` (Google OIDC token from Cloud Tasks)
- `GET /addresses/google/callback`, `GET /addresses/outlook/callback` (OAuth bouncer pages)
- `POST /oauth/token`, `POST /oauth/revoke` (OAuth token endpoints, authenticated by the client's id and secret)
- `GET /.well-known/oauth-authorization-server` (OAuth discovery metadata)

## Notes

- When a route has both a JWT permission and an API permission listed, the dual-auth middleware checks the JWT user's organization role for browser callers and the key's permission bitmask for API key callers. They're independent gates: a user's role doesn't constrain what an API key can do beyond what was granted at creation.
- Every API key request is logged to `api_key_usage_logs` with the endpoint pattern (e.g. `/campaigns/:id`), method, IP, user-agent, response status, and elapsed milliseconds. JWT requests are not logged here.
- Rate limit categories (`X-RateLimit-*` headers) are scoped to the underlying user, not the key; every key issued by a user shares the user's quota.

## See also

- [Authentication](/api/authentication)
- [Permissions Reference](/api/permissions)
- [Error Codes](/api/error-codes)
