# REST API

> Every route under booleansmtp/v1, free and Pro — 83 endpoints total, from the plugin's own route definitions.

```
{site_url}/wp-json/booleansmtp/v1/
```

Every route needs an authenticated WordPress request and, unless noted otherwise, the
`manage_options` capability — filterable with `boolean_smtp_rest_capability`. The plugin adds no
REST-specific auth of its own, so it takes whatever WordPress core already accepts:

- **From the browser** (the admin UI itself): a logged-in session plus a REST nonce (`X-WP-Nonce`).
- **From Postman, a script, or any external client**: a WordPress [Application
  Password](https://make.wordpress.org/core/2020/11/05/application-passwords-integration-guide/)
  (**Users → Profile → Application Passwords** on the site, then HTTP Basic Auth with the site
  username and the generated password) — no nonce needed. Nothing in the plugin blocks or checks
  for this; if an account can `manage_options`, its Application Password can call every route below,
  free or Pro.

<Aside type="note" title="Planned: Pro-only external API access">
	The owner plans to make external REST access itself a licensed Pro capability — separate from the per-route
	`feature:*` gates already on some Pro routes below. **Not built yet**: every route today, including the free
	plugin's, answers any authenticated request as described above. Tracked in the plugin's `TODO.md` under "External
	REST API access".
</Aside>

**The free plugin registers no public route.** Pro's one exception is the provider webhook receiver, verified against a
secret instead of authentication (see [Webhooks](#webhooks-pro)). There is no generic "send an arbitrary
email" endpoint: sending goes through `wp_mail()`, not the REST API.

## Errors

```json
{
  "code": "validation_error",
  "message": "The API key is required.",
  "data": { "status": 422, "errors": { "api_key": "API key is required." } }
}
```

| Status | Code | When |
| --- | --- | --- |
| 400 | `bad_request` | Malformed request body |
| 401 | `rest_not_logged_in` | Missing or invalid nonce |
| 403 | `rest_forbidden` | Insufficient capability |
| 404 | `not_found` | Resource does not exist |
| 422 | `validation_error` | Field validation failed |
| 500 | `internal_error` | Unhandled server error |

## Free plugin (48 routes)

### Dashboard

| Method | Route | Description |
| --- | --- | --- |
| `GET` | `/dashboard/stats` | Aggregate email statistics. |
| `GET` | `/dashboard/chart` | Chart data for email volume. |
| `GET` | `/dashboard/activity` | Recent failed or delivered messages. |
| `GET` | `/dashboard/onboarding` | Onboarding state. |
| `POST` | `/dashboard/onboarding` | Update onboarding state. |
| `POST` | `/dashboard/onboarding/apply` | Apply the onboarding wizard's chosen connection. |

### Connections

| Method | Route | Description |
| --- | --- | --- |
| `GET` | `/connections` | List all connections. |
| `POST` | `/connections` | Create a connection. |
| `GET` | `/connections/{id}` | Get a single connection. |
| `PUT` | `/connections/{id}` | Update a connection's settings. |
| `DELETE` | `/connections/{id}` | Delete a connection. |
| `DELETE` | `/connections` | Bulk delete connections. |
| `POST` | `/connections/{id}/test` | Send a real test email through this connection — see [`boolean_smtp_test_email_sent`](/hooks/boolean_smtp_test_email_sent/). |
| `POST` | `/connections/{id}/verify-credentials` | Verify SMTP credentials without sending. |
| `POST` | `/connections/{id}/verify-api-credentials` | Verify API-mode credentials without sending. |
| `POST` | `/connections/{id}/oauth-token` | Store an OAuth token exchanged out of band. |
| `POST` | `/connections/{id}/oauth-refresh-now` | Force an immediate OAuth token refresh. |
| `GET` | `/connections/{id}/oauth-refresh-history` | Paginated refresh-attempt history. |

```json title="POST /connections"
{
  "name": "Production SES",
  "driver": "ses",
  "is_active": true,
  "settings": {
    "delivery_mode": "api",
    "region": "us-east-1",
    "access_key": "AKIA...",
    "secret": "wJal...",
    "from_email": "noreply@example.com",
    "from_name": "My App"
  }
}
```

`settings` is validated per-transport (`TransportContract::validateSettings()`) and its sensitive
fields — anything matching `key`, `secret`, `password`, `token` or `username`, plus whatever
[`boolean_smtp_sensitive_keys`](/hooks/boolean_smtp_sensitive_keys/) adds — come back masked, never
in full.

### Transports

| Method | Route | Description |
| --- | --- | --- |
| `GET` | `/transports` | Settings schema, validation rules and presets for every registered transport. |
| `GET` | `/transports/{driver}` | Same, for one driver (`ses`, `sendgrid`, `smtp`, …). The field names [`wp-config` credentials](/constants/) use. |

### OAuth

The manual-app flows for Google, Microsoft/Outlook and Zoho:

| Method | Route |
| --- | --- |
| `GET` | `/oauth/google/authorize` |
| `GET` | `/oauth/google/callback` |
| `GET` | `/oauth/microsoft/authorize` |
| `GET` | `/oauth/microsoft/callback` |
| `GET` | `/oauth/zoho/authorize` |
| `GET` | `/oauth/zoho/callback` |

`authorize` takes `?connection_id=` and redirects to the provider's consent screen; `callback`
exchanges the code for tokens, stores them encrypted on the connection, and redirects back to the
plugin's admin page. See [wp-config: OAuth redirects](/constants/#oauth-redirects-google-microsoft-zoho)
for how the redirect URI itself is chosen.

### Email logs

| Method | Route | Description |
| --- | --- | --- |
| `GET` | `/logs` | List logs — paginated, filterable by `status`, `per_page`, `page`, `search`. |
| `GET` | `/logs/queue/stats` | Queued / sending / failed / delivered counts. |
| `GET` | `/logs/{id}` | A single log, including its body. |
| `POST` | `/logs/resend` | Bulk resend. |
| `POST` | `/logs/{id}/resend` | Resend one email. |
| `DELETE` | `/logs/{id}` | Delete one log. |
| `DELETE` | `/logs` | Bulk delete. |

### Test email

| Method | Route |
| --- | --- |
| `POST` | `/test-email` |

```json title="POST /test-email"
{ "to": "test@example.com", "connection_id": 1, "subject": "Test Email (optional)" }
```

### Settings

| Method | Route |
| --- | --- |
| `GET` | `/settings` |
| `PUT` | `/settings` |

Keys the admin UI's Settings screen shows: `from_email`, `from_name`, `default_connection_id`,
`fallback_connection_id`, `simulation_enabled`, `log_emails`, `log_retention_days`. Keys it does not
show, that `PUT /settings` still accepts:

| Key | Default | Meaning |
| --- | --- | --- |
| `health_check_enabled` | `true` | Probe every active connection on a schedule. |
| `health_check_interval` | `15` | Minutes between probes (1–1440). |
| `oauth_refresh_enabled` | `true` | Refresh OAuth tokens before they expire. |
| `oauth_refresh_interval` | `15` | Minutes between refresh runs (1–1440). |
| `oauth_refresh_max_retries` | `3` | Attempts per token before the failure is reported (1–10). |

The log retention period also has a filter: [`boolean_smtp_log_retention_days`](/hooks/boolean_smtp_log_retention_days/)
(return `0` to keep logs forever).

### Notifications

| Method | Route | Description |
| --- | --- | --- |
| `GET` | `/notifications` | List alert channels. |
| `POST` | `/notifications` | Create a channel. |
| `POST` | `/notifications/bulk` | Bulk action on channels. |
| `PUT` | `/notifications/{id}` | Update a channel. |
| `DELETE` | `/notifications/{id}` | Delete a channel. |
| `POST` | `/notifications/test` | Send a test notification. |
| `POST` | `/notifications/telegram/detect-chats` | Detect the chat ids a Telegram bot can post to. |

### Tools

| Method | Route | Description |
| --- | --- | --- |
| `GET` | `/tools/debug-logs` | Stored SMTP debug transcripts. |
| `DELETE` | `/tools/debug-logs` | Clear stored transcripts. |
| `GET` | `/tools/plugins` | Active plugins on the site. |
| `GET` | `/tools/migration/scan` | Other SMTP plugins found on the site (`?counts=false` skips row counts). |
| `POST` | `/tools/migration/import` | Assess (`dry_run`) or import a plugin's connections and one chunk of its log. |

`POST /tools/migration/import` (`MigrationImportRequest`): `source` (required — the same ids as
[`wp boolean-smtp import`](/wp-cli/#import)), `dry_run`, `import_connections` (default `true`),
`import_logs` (default `false`; one chunk of 200 rows per call — repeat until `logs.done`),
`max_logs` (0–20000), `restart_logs`, `retention_days` (dry run only). Response: `connections` (each
with `status` of `ready`, `needs_password`, `needs_authorization`, `needs_review` or `unsupported`,
plus `missing`, `issues`, `conversion`, `was_default`, `connection_id`, masked `settings`), `logs`
(`available`, `reason`, `total`, `imported`, `skipped`, `cursor`, `done`), `suggestions`, `errors`.
422 for an unknown or unavailable source.

## BooleanSMTP Pro (35 routes)

Everything below lives under `booleansmtp/v1/pro/*` and only exists when Pro is active — the free
plugin has none of it. <Badge text="Pro" variant="tip" size="small" /> throughout.

### License

| Method | Route |
| --- | --- |
| `GET` | `/pro/license/status` |
| `POST` | `/pro/license/activate` |
| `POST` | `/pro/license/deactivate` |
| `POST` | `/pro/license/refresh` |

No feature gate: reachable once authenticated, license or not — everything else below needs one.

### Pro settings

| Method | Route | Description |
| --- | --- | --- |
| `GET` | `/pro/settings` | `max_retries`, `plugin_exclusions_enabled`, `disabled_log_sources`. |
| `PUT` | `/pro/settings` | Update them, in the add-on's own settings scope. |

### Analytics

| Method | Route |
| --- | --- |
| `GET` | `/pro/analytics/overview` |
| `GET` | `/pro/analytics/deliverability` |
| `GET` | `/pro/analytics/export` |

### Tools

| Method | Route | Description |
| --- | --- | --- |
| `GET` | `/pro/tools/domain-check?domain=` | SPF, DKIM, DMARC and MX of a sending domain. 403 `license_required` without a license. |
| `POST` | `/pro/tools/export` | Settings snapshot without secrets, for moving between sites. |
| `POST` | `/pro/tools/import/preview` | `{ data }` → `conflicts` and `skipped`; writes nothing. |
| `POST` | `/pro/tools/import` | `{ data, resolutions }` → counts, `kept`, `replaced`, `skipped`. |

### Sender groups

Every sender with more than one connection, and the failover/balance/overflow strategy across them.

| Method | Route | Description |
| --- | --- | --- |
| `GET` | `/pro/sender-groups` | Each group: `sender`, `strategy`, `members` (`connection_id`, `name`, `provider`, `is_active`, `health_status`, `weight`, `daily_cap`, `sent_today`) in failover order. |
| `PUT` | `/pro/sender-groups` | `sender`, `strategy` (`failover` \| `balance` \| `overflow`), `members` in order. 422 when a member does not send as `sender`. |

### Routing rules

| Method | Route | Description |
| --- | --- | --- |
| `GET` | `/pro/routing-rules` | List all rules. |
| `POST` | `/pro/routing-rules` | Create a rule. |
| `GET` | `/pro/routing-rules/{id}` | Get one rule. |
| `PUT` | `/pro/routing-rules/{id}` | Update a rule. |
| `DELETE` | `/pro/routing-rules/{id}` | Delete a rule. |
| `POST` | `/pro/routing-rules/reorder` | Reorder priorities. |
| `POST` | `/pro/routing-rules/bulk` | Bulk action. |
| `POST` | `/pro/routing-rules/test` | Test a rule against sample message data. |

```json title="POST /pro/routing-rules"
{ "name": "Marketing to SES", "conditions": [], "connection_id": 3, "priority": 10 }
```

### SES identity management

Relocated from the free plugin 2026-09-19; needs the `connections.ses.identity_management` feature.

| Method | Route | Description |
| --- | --- | --- |
| `POST` | `/pro/ses/identities` | List SES verified identities. |
| `POST` | `/pro/ses/valid-senders` | Valid sender addresses/domains for the connection. |
| `POST` | `/pro/ses/identities/action` | Verify or delete an identity. |
| `POST` | `/pro/ses/stats` | Sending statistics from the SES API. |
| `POST` | `/pro/ses/credential-sources` | Available credential sources: database, wp-config or environment. |

### One-click OAuth (Google, Microsoft)

A hosted-proxy OAuth flow — relocated from the free plugin's `OAuthController` alongside the SES
routes above.

| Method | Route | Capability |
| --- | --- | --- |
| `GET` | `/pro/oauth/google/one-click/authorize` | `manage_options` + `feature:connections.oneclick.save` |
| `GET` | `/pro/oauth/google/one-click/callback` | Public callback |
| `POST` | `/pro/oauth/google/one-click/callback` | Public callback |
| `GET` | `/pro/oauth/microsoft/one-click/authorize` | `manage_options` + `feature:connections.oneclick.save` |
| `GET` | `/pro/oauth/microsoft/one-click/callback` | Public callback |
| `POST` | `/pro/oauth/microsoft/one-click/callback` | Public callback |

### Webhooks (Pro)

| Method | Route | Auth |
| --- | --- | --- |
| `POST` | `/pro/webhooks/{provider}` | Public, verified — not a nonce |

The only public route in the plugin. Every call is verified against a secret stored on the
provider's connection before it touches a log row: SendGrid (ECDSA signature over
`timestamp + body`), Mailgun (HMAC-SHA256 of `timestamp + token`), Postmark (HTTP basic auth
password). Amazon SES arrives on the same route through its SNS handler
(`boolean_smtp_pro_webhook_ses`). 401 when no secret is configured or the signature does not
verify; a verified event updates the matching log's status and fires
`boolean_smtp_pro_webhook_received`.

## Next steps

- [Extending: custom transport](/extending/custom-transport/) — `GET /transports/{driver}` describes
  every transport this way, including one you register yourself.
- [Hooks: connections and OAuth](/hooks/connections/) — the events these routes fire.
