# Mailprotector CloudFilter REST API — Reference Full endpoint catalog and filter tables for the `mailprotector` skill. SKILL.md stays lean; the detail lives here. ## Connection | Item | Value | |---|---| | Base URL | `https://emailservice.io/api/v1` | | Override env | `MAILPROTECTOR_API_BASE_URL` | | Auth | `Authorization: Bearer ` | | Key env override | `MAILPROTECTOR_API_KEY` | | Vault entry | `msp-tools/mailprotector.sops.yaml`, field `credentials.api_key` | Credential resolution order: `MAILPROTECTOR_API_KEY` env -> vault `credentials.api_key` (read via `bash /.claude/scripts/vault.sh get-field`). A clear setup error is raised if neither resolves. ## Scopes The five entity types that carry `logs`, `messages`, `configuration`, `users`, `domains`, and `allow_block_rules` sub-resources. Path form is `/{scope}/{id}/...`: ``` resellers, customers, domains, user_groups, users ``` The CLI validates `scope` against this set. ## Pagination | Param | Default | Notes | |---|---|---| | `page` | 1 | 1-indexed page number | | `per_page` | 25 | Max **50** on reseller `messages` | The response includes an `X-Pagination` response header (a JSON document with the page/total metadata). ## Global list filtering List endpoints accept `field[op]=value` filters. Operators: | Op | Meaning | |---|---| | `Gt` | greater than | | `Geq` | greater than or equal | | `Lt` | less than | | `Leq` | less than or equal | | `Eq` | equal | Example: `created_at[geq]=2026-06-01`. ## Logs / messages filtering Every `.../logs` and `.../messages` endpoint accepts these params: | Param | Default | Allowed values | |---|---|---| | `sort_direction` | `desc` | `desc`, `asc` | | `sort_field` | `@timestamp` | `@timestamp`, `prime.direction`, `prime.from_header_raw`, `prime.recipient`, `prime.subject`, `prime.decision`, `prime.score` | | `page` | 1 | integer | | `page_size` | (API default) | integer | | `sender` | (none) | sender filter | | `recipient` | (none) | recipient filter | | `subject` | (none) | subject filter | | `decision` | `all` | `default`, `deliver`, `quarantine_spam`, `quarantine_virus`, `quarantine_policy`, `bounce`, `encrypt`, `delete` | ## READ endpoints | Method | Path | Client method | CLI | |---|---|---|---| | GET | `/domains` | `domains()` | `domains` | | GET | `/{scope}/{id}/domains` | `domains(scope,id)` | `domains --scope --id` | | GET | `/domains/{id}` | `domain(id)` | `domain ` | | GET | `/resellers/{id}/customers` | `customers(id)` | `customers ` | | GET | `/customers/{id}` | `customer(id)` | `customer ` | | GET | `/{scope}/{id}/users` | `users(scope,id)` | `users ` | | GET | `/users/{id}` | `user(id)` | `user ` | | POST | `/users/find_by_address` | `find_user(address)` | `find-user
` | | GET | `/{scope}/{id}/logs` | `logs(scope,id,...)` | `logs ` | | GET | `/{scope}/{id}/messages` | `messages(scope,id,...)` | `messages ` | | GET | `/{scope}/{id}/configuration` | `configuration(scope,id)` | `config ` | | GET | `/{scope}/{id}/allow_block_rules` | `allow_block_rules(scope,id)` | `rules ` | **`find_by_address` is a READ** despite being a POST — it looks up a user / alias by email. It is NOT gated behind `--confirm`. `status` is a synthetic read: `GET /domains?per_page=1` used purely to validate the bearer token (HTTP 200 = key good). ## WRITE endpoints (gated behind `--confirm`) Without `--confirm` the CLI prints `[DRY RUN] Would : ` and exits with code 2. With `--confirm` it performs the call. ### Release one held message ``` POST /messages/{message_id}/deliver body: {"include_original_recipients": 1, "recipients": ""} ``` Client: `release_message(message_id, recipients=None)` — CLI: `release [--recipients csv] --confirm` ### Bulk release held messages ``` POST /{scope}/{id}/messages/deliver_many body: {"include_original_recipients": 1, "recipients": "", "all_selected": false, "ids": ""} ``` Client: `release_many(scope, id, ids=None, all_selected=False, recipients=None)` CLI: `release-many [--ids csv | --all] [--recipients csv] --confirm` ### Add allow / block rule ``` POST /{scope}/{id}/allow_block_rules body: {"value": "...", "rule_type": "allow" | "block"} ``` Client: `add_rule(scope, id, value, rule_type)` — CLI: `add-rule --value --type allow|block --confirm` ### Enable spam release on an entity ``` PUT /{scope}/{id}/configuration body: {"permissions": {"messages": {"allow_spam_release": true}}} ``` Client: `enable_release(scope, id)` — CLI: `enable-release --confirm` This is required before an entity's held **spam** can be released. Check the state first with `config ` and look at `permissions.messages.allow_spam_release`. ## Raw escape hatch ``` py mp.py raw [--body JSON] [--confirm] ``` Non-GET methods require `--confirm`. Use for any endpoint not wrapped by a named command. ## The `allow_spam_release` gotcha Releasing a held **spam** message will fail (or silently no-op) if the owning entity does not have `permissions.messages.allow_spam_release = true`. The fix: 1. `py mp.py config ` — confirm `allow_spam_release` is `false`. 2. `py mp.py enable-release --confirm` — flip it to `true`. 3. Re-run the `release` / `release-many`. Virus and policy quarantines are governed separately — only spam release is gated by this permission.