The provisioning API is for platform services such as VaultDrop that set up mail and work-suite accounts for their teams. End users never call it; they use JMAP, IMAP and SMTP with their own credentials.
Authentication. Every request carries the platform API key as a bearer token: Authorization: Bearer cs_pk_.... The server stores only the key's SHA-256 (CORPSUITE_PLATFORM_API_KEY_SHA256); generate a pair with corpsuited gen-platform-key. When no hash is configured, bearer tokens are refused with 401.
The SaaS admin panel at /admin uses the same routes with a session instead: it signs in with POST /v1/session, which sets an HttpOnly, Secure, SameSite=Strict cookie, and sends the session's CSRF token in X-CSRF-Token on every call. A request that carries an Authorization header is judged by that header alone. Administrators are created on the server with corpsuited set-admin EMAIL.
Team administrators. A team's owners and admins, on a plan with the team_admin entitlement, can call the operations marked x-team: true for their own team only, from the domain admin panel at /manage. They sign in with webmail's POST /api/mail/session and send its cookie and X-CSRF-Token; the session must be under 12 hours old and used within the hour. Calls for another tenant, or to unmarked operations, answer 403. Team admins also cannot: change an owner unless they are one, make owners unless they are one, change their own role or suspend themselves, change team-wide or domain-wide filters unless they are an owner, set externalRef, raise a user's limits above the plan, or add users and aliases outside the team's own domains. They add domains through /pending-domains, proving each with a DNS TXT record, never with POST /domains. See docs/kb/team-admin.md.
Staff permissions. A panel administrator signed in with a session may call an operation only with its permission: read for every GET, and for changes tenants, approvals, reports, plans, settings, legal, impersonate or staff by area (* is everything). The platform API key and the command line have every permission. See /v1/staff.
Team API keys. A team on a plan with api_access and team_admin makes keys with /api-keys. A request with Authorization: Bearer cs_tk_... may call the x-team operations for that team, with an admin's rights (never an owner's), except the /api-keys operations.
Tenancy. Everything below /v1/tenants/{tenantId} is looked up by the tenant id as well as its own id. An id that belongs to another tenant answers 404, exactly like an id that does not exist.
Errors use RFC 9457 problem details (application/problem+json). Request bodies are JSON objects of at most 64 KiB; unknown fields are rejected with 400 rather than ignored.
Plans. Every tenant is on a plan (free, solo or team kind) that sets its limits and entitlements. Move a tenant to another plan with PATCH /v1/tenants/{tenantId} and {"plan": "solo"}; that is how a free account becomes a paid solo account and a solo account becomes a team. Requests that would pass a limit answer 409 with limit, used and max in the problem body. The /v1/plans, /v1/settings and /v1/shared-domains routes are the SaaS administrator's controls, and self-service signup (/api/signup, described in api/signup.yaml) obeys them. Webmail's sign-in (/api/mail/session) is described in api/webmail.yaml; it is allowed only on plans with the webmail entitlement.
Mapping from VaultDrop. Store VaultDrop's team id in the tenant's externalRef and its user id in the account's externalRef, then look tenants up with GET /v1/tenants?externalRef=. VaultDrop roles map to Zappocity roles as manager → admin, member → member, external → guest.
Accounts
/v1/accounts
Find mailboxes of every team
Address, name or team name containing q (every mailbox when empty), newest first. For the Zappocity panel's Accounts page; platform administrators only.
| Parameter | In | Type | |
|---|---|---|---|
q | query | string | |
limit | query | integer |
200The mailboxes, as GET .../accounts/{accountId} gives them.{ "items": [ { "address": "carol@acme.test", "displayName": "Carol", "id": "0b6e4c1e-9a0e-4c55-8f0b-3d1f6c1d2e3f", "role": "member", "status": "active", "tenantId": "4ee2971f-0fae-4230-8512-ddf1704a769d" } ] }401The platform API key is missing or wrong, or there is no valid admin panel session.
/v1/tenants/{tenantId}/accounts
team admins
List the tenant's accounts
| Parameter | In | Type | |
|---|---|---|---|
tenantId * | path | string |
200Accounts, oldest first.401The platform API key is missing or wrong, or there is no valid admin panel session.404No such object in this tenant (including objects of other tenants).
/v1/tenants/{tenantId}/accounts
team admins
Create an account
The address must be on one of the tenant's domains, must not contain a + tag, and must not clash with an alias. The tenant's seat limit applies (409 when full). Without password the account cannot sign in with a password; single sign-on arrives in milestone 1. INBOX, Drafts, Sent, Archive, Junk and Trash are created with it.
| Parameter | In | Type | |
|---|---|---|---|
tenantId * | path | string |
201Created.400The body is not a valid JSON object, has unknown fields, or is too large.401The platform API key is missing or wrong, or there is no valid admin panel session.404No such object in this tenant (including objects of other tenants).409The name or address is taken, the tenant has no free seats, a plan limit would be passed (the body adds limit, used and max), or the object is still in use.422A value is out of range, or names a domain or account of another tenant. When one setting is refused, `field` names it and `detail` says what is allowed.
/v1/tenants/{tenantId}/accounts/{accountId}
team admins
Get an account
| Parameter | In | Type | |
|---|---|---|---|
tenantId * | path | string | |
accountId * | path | string |
200The account.401The platform API key is missing or wrong, or there is no valid admin panel session.404No such object in this tenant (including objects of other tenants).
/v1/tenants/{tenantId}/accounts/{accountId}
team admins
Change an account's name, role, status, password or recovery address
| Parameter | In | Type | |
|---|---|---|---|
tenantId * | path | string | |
accountId * | path | string |
200The updated account.400The body is not a valid JSON object, has unknown fields, or is too large.401The platform API key is missing or wrong, or there is no valid admin panel session.404No such object in this tenant (including objects of other tenants).422A value is out of range, or names a domain or account of another tenant. When one setting is refused, `field` names it and `detail` says what is allowed.
/v1/tenants/{tenantId}/accounts/{accountId}/api-tokens
A mailbox's API tokens
For services that send as the mailbox (POST /api/mail/send, scope send) or read its mail over JMAP (scope jmap). The tokens themselves are not shown again.
| Parameter | In | Type | |
|---|---|---|---|
tenantId * | path | string | |
accountId * | path | string |
200The tokens.{ "items": [ { "createdAt": "2026-10-08T12:00:00Z", "createdBy": "the platform API key", "id": "5d1e2f3a-4b5c-4d6e-8f70-819203a4b5c6", "lastUsedAt": null, "name": "Zappocity mail", "scopes": [ "send" ] } ] }401The platform API key is missing or wrong, or there is no valid admin panel session.404No such object in this tenant (including objects of other tenants).
/v1/tenants/{tenantId}/accounts/{accountId}/api-tokens
Make an API token for the mailbox
The token (cs_mt_...) is in the answer once; only its hash is kept. At most 20 per mailbox.
| Parameter | In | Type | |
|---|---|---|---|
tenantId * | path | string | |
accountId * | path | string |
{
"name": "Zappocity mail",
"scopes": [
"send"
]
}
201The token, shown once.{ "createdBy": "the platform API key", "id": "5d1e2f3a-4b5c-4d6e-8f70-819203a4b5c6", "name": "Zappocity mail", "scopes": [ "send" ], "token": "cs_mt_Yk3" }401The platform API key is missing or wrong, or there is no valid admin panel session.404No such object in this tenant (including objects of other tenants).409The name or address is taken, the tenant has no free seats, a plan limit would be passed (the body adds limit, used and max), or the object is still in use.422A value is out of range, or names a domain or account of another tenant. When one setting is refused, `field` names it and `detail` says what is allowed.
/v1/tenants/{tenantId}/accounts/{accountId}/api-tokens/{tokenId}
Revoke a mailbox API token
| Parameter | In | Type | |
|---|---|---|---|
tenantId * | path | string | |
accountId * | path | string | |
tokenId * | path | string |
204Revoked.401The platform API key is missing or wrong, or there is no valid admin panel session.404No such object in this tenant (including objects of other tenants).
/v1/tenants/{tenantId}/accounts/{accountId}/app-passwords
team admins
List an account's app passwords
The app passwords the user made in webmail for their mail apps: names and dates only. The passwords themselves are stored hashed and are never shown again. See docs/kb/two-step-sign-in.md.
| Parameter | In | Type | |
|---|---|---|---|
tenantId * | path | string | |
accountId * | path | string |
200The app passwords, oldest first.{ "items": [ { "createdAt": "2026-10-07T09:12:00Z", "id": "3f0c9a52-1b7e-4d1a-9c55-0d6f2f1f4a10", "lastUsedAt": "2026-10-07T18:40:11Z", "name": "Phone" } ] }401The platform API key is missing or wrong, or there is no valid admin panel session.404No such object in this tenant (including objects of other tenants).
/v1/tenants/{tenantId}/accounts/{accountId}/app-passwords/{appPasswordId}
team admins
Revoke one app password
For a lost phone or laptop. The app password stops working at once for new sign-ins; IMAP connections already signed in with it are ended when they next check the account (within seconds).
| Parameter | In | Type | |
|---|---|---|---|
tenantId * | path | string | |
accountId * | path | string | |
appPasswordId * | path | string |
204The app password is revoked.401The platform API key is missing or wrong, or there is no valid admin panel session.404No such object in this tenant (including objects of other tenants).
/v1/tenants/{tenantId}/accounts/{accountId}/calendar-links
team admins
A mailbox's calendar subscription links
Secret iCalendar feeds of the user's own calendars, which anyone with the address can subscribe to: full details or only busy times. The address is shown only once, to the user who made the link, so it is not listed here. lastUsed is when an app last fetched it. Users manage their own in webmail Settings; see docs/kb/calendars.md.
| Parameter | In | Type | |
|---|---|---|---|
tenantId * | path | string | |
accountId * | path | string |
200The links.{ "items": [ { "calendarId": "K12", "calendarName": "Work", "createdAt": "2026-10-08T15:00:00Z", "detail": "busy", "id": "3d0c8e52-7a41-4f7e-b6a1-0c2e9d5f8a34", "label": "For clients", "lastUsed": "2026-10-08T16:05:00Z" } ] }401The platform API key is missing or wrong, or there is no valid admin panel session.403A team admin asked for something only the platform operator may do, or for another team, or the CSRF token is missing or wrong.404No such object in this tenant (including objects of other tenants).
/v1/tenants/{tenantId}/accounts/{accountId}/calendar-links/{linkId}
team admins
Revoke a calendar subscription link
The address stops working at once; apps subscribed to it stop updating.
| Parameter | In | Type | |
|---|---|---|---|
tenantId * | path | string | |
accountId * | path | string | |
linkId * | path | string |
204Revoked.401The platform API key is missing or wrong, or there is no valid admin panel session.403A team admin asked for something only the platform operator may do, or for another team, or the CSRF token is missing or wrong.404No such object in this tenant (including objects of other tenants).
/v1/tenants/{tenantId}/accounts/{accountId}/domain-blocks
team admins
A user's domain blocks
Mail from other servers is refused, or filed in Junk, when a block matches: kind domain matches the sender's domain (envelope and From) and its subdomains, mx the mail servers of the sender's domain, and named any domain the message names (From, Sender, Reply-To and links in its text). The platform's, the team's and the user's blocks all apply. See docs/kb/domain-blocking.md.
| Parameter | In | Type | |
|---|---|---|---|
tenantId * | path | string | |
accountId * | path | string |
200The blocks.{ "items": [ { "action": "reject", "createdAt": "2026-10-09T12:00:00Z", "createdBy": "admin@example.com", "id": 7, "kind": "mx", "note": "Snowshoe spam", "value": "bulk-mailer.example" } ] }401The platform API key is missing or wrong, or there is no valid admin panel session.403A team admin asked for something only the platform operator may do, or for another team, or the CSRF token is missing or wrong.404No such object in this tenant (including objects of other tenants).
/v1/tenants/{tenantId}/accounts/{accountId}/domain-blocks
team admins
Block a domain
| Parameter | In | Type | |
|---|---|---|---|
tenantId * | path | string | |
accountId * | path | string |
{
"action": "reject",
"kind": "named",
"note": "Links in the invoice scam",
"value": "phish.example"
}
201The block.401The platform API key is missing or wrong, or there is no valid admin panel session.403A team admin asked for something only the platform operator may do, or for another team, or the CSRF token is missing or wrong.404No such object in this tenant (including objects of other tenants).409The name or address is taken, the tenant has no free seats, a plan limit would be passed (the body adds limit, used and max), or the object is still in use.422A value is out of range, or names a domain or account of another tenant. When one setting is refused, `field` names it and `detail` says what is allowed.
/v1/tenants/{tenantId}/accounts/{accountId}/domain-blocks/{blockId}
team admins
Remove a domain block
| Parameter | In | Type | |
|---|---|---|---|
tenantId * | path | string | |
accountId * | path | string | |
blockId * | path | integer |
204Removed.401The platform API key is missing or wrong, or there is no valid admin panel session.403A team admin asked for something only the platform operator may do, or for another team, or the CSRF token is missing or wrong.404No such object in this tenant (including objects of other tenants).
/v1/tenants/{tenantId}/accounts/{accountId}/entitlements
team admins
A user's features, from the plan and their own switches
| Parameter | In | Type | |
|---|---|---|---|
tenantId * | path | string | |
accountId * | path | string |
200The plan's entitlements, the user's switches and the result.401The platform API key is missing or wrong, or there is no valid admin panel session.403A team admin asked for something only the platform operator may do, or for another team, or the CSRF token is missing or wrong.404No such object in this tenant (including objects of other tenants).
/v1/tenants/{tenantId}/accounts/{accountId}/entitlements
team admins
Turn features off for one user, or give them more
off lists plan entitlements this user does not get; on lists entitlements they get although the plan lacks them. Both replace the current lists; leave on out to keep it. Teams may only change off. Changes apply from the user's next request; open IMAP sessions see them at their next account recheck.
| Parameter | In | Type | |
|---|---|---|---|
tenantId * | path | string | |
accountId * | path | string |
{
"off": [
"imap",
"forwarding"
]
}
200The new state.400The body is not a valid JSON object, has unknown fields, or is too large.401The platform API key is missing or wrong, or there is no valid admin panel session.403A team admin asked for something only the platform operator may do, or for another team, or the CSRF token is missing or wrong.404No such object in this tenant (including objects of other tenants).422A value is out of range, or names a domain or account of another tenant. When one setting is refused, `field` names it and `detail` says what is allowed.
/v1/tenants/{tenantId}/accounts/{accountId}/impersonate
team admins
Make a link that opens webmail as the user
For support. Needs the team's impersonation setting: operator (platform staff with the impersonate permission, and the API key) or admins (also the team's owners and admins; an admin cannot open an owner, nobody their own account). The link works once, for five minutes, and opens a one-hour webmail session marked with who opened it and why; it cannot change the password, recovery address, two-step sign-in, app passwords or alerts. The user is emailed and it shows in their sign-in history.
| Parameter | In | Type | |
|---|---|---|---|
tenantId * | path | string | |
accountId * | path | string |
{
"reason": "Ticket 4182: filters not working"
}
201The link.{ "expiresAt": "2026-10-08T12:05:00Z", "url": "https://webmail.example.com/#impersonate=Qm1..." }401The platform API key is missing or wrong, or there is no valid admin panel session.403A team admin asked for something only the platform operator may do, or for another team, or the CSRF token is missing or wrong.404No such object in this tenant (including objects of other tenants).422A value is out of range, or names a domain or account of another tenant. When one setting is refused, `field` names it and `detail` says what is allowed.
/v1/tenants/{tenantId}/accounts/{accountId}/imports
team admins
A user's imports from other mail servers, newest first
| Parameter | In | Type | |
|---|---|---|---|
tenantId * | path | string | |
accountId * | path | string |
200The imports.401The platform API key is missing or wrong, or there is no valid admin panel session.403A team admin asked for something only the platform operator may do, or for another team, or the CSRF token is missing or wrong.404No such object in this tenant (including objects of other tenants).
/v1/tenants/{tenantId}/accounts/{accountId}/imports
team admins
Import a user's mail from another server over IMAP
Copies every remote folder in the background: INBOX, Sent, Drafts, Junk, Trash and Archive (by their special-use flags or usual names) into the matching folders here, other folders into folders of the same name. Read, starred, answered and draft marks and the received date come along; deleted messages and virtual folders (All Mail, Starred) are left out. An import resumes where it stopped and skips messages already here (same Message-ID and size), so running it again copies only what is new. It stops when the mailbox is full. One import per account at a time; the user is emailed when it ends.
| Parameter | In | Type | |
|---|---|---|---|
tenantId * | path | string | |
accountId * | path | string |
{
"host": "imap.gmail.com",
"password": "app password",
"skipFolders": [
"[Gmail]/Spam"
],
"username": "amy@gmail.com"
}
201Queued.401The platform API key is missing or wrong, or there is no valid admin panel session.403A team admin asked for something only the platform operator may do, or for another team, or the CSRF token is missing or wrong.404No such object in this tenant (including objects of other tenants).409The name or address is taken, the tenant has no free seats, a plan limit would be passed (the body adds limit, used and max), or the object is still in use.422A value is out of range, or names a domain or account of another tenant. When one setting is refused, `field` names it and `detail` says what is allowed.
/v1/tenants/{tenantId}/accounts/{accountId}/imports/{importId}
team admins
Cancel a queued or running import
What was copied stays. 409 when it has already ended.
| Parameter | In | Type | |
|---|---|---|---|
tenantId * | path | string | |
accountId * | path | string | |
importId * | path | string |
204Cancelled.401The platform API key is missing or wrong, or there is no valid admin panel session.403A team admin asked for something only the platform operator may do, or for another team, or the CSRF token is missing or wrong.404No such object in this tenant (including objects of other tenants).409The name or address is taken, the tenant has no free seats, a plan limit would be passed (the body adds limit, used and max), or the object is still in use.
/v1/tenants/{tenantId}/accounts/{accountId}/legal-export
Export an account's records for a lawful request
Answers one ZIP file: README.txt, account.json (the account, its company and its signup checks), sign-ins.csv, webmail-sessions.csv, aliases.json (with app password names), messages.csv (every message received in the period with its folders, sender, recipients, subject and Message-ID), mbox/ (the messages themselves, one mbox file per folder, only with content), filters.json, imports.json, spam-reports.json, and manifest.json with every file's SHA-256. The period is from-to (dates or RFC 3339 times; all time by default). include chooses the parts; without it, everything but content. Each export is recorded in GET /v1/legal-exports with who made it and the file's SHA-256 (also in X-Export-SHA256). For the platform operator only.
| Parameter | In | Type | |
|---|---|---|---|
tenantId * | path | string | |
accountId * | path | string |
{
"caseReference": "Subpoena 2026-117",
"from": "2026-01-01",
"include": [
"account",
"logins",
"messages",
"content"
],
"to": "2026-10-01"
}
200The ZIP file.401The platform API key is missing or wrong, or there is no valid admin panel session.404No such object in this tenant (including objects of other tenants).422A value is out of range, or names a domain or account of another tenant. When one setting is refused, `field` names it and `detail` says what is allowed.
/v1/tenants/{tenantId}/accounts/{accountId}/limits
team admins
Show one user's limits and usage
The plan's per-user limits, the user's own overrides (null where the plan's value applies), the limits in effect, and what the user has used: recipients sent to in the last hour and day, and mail stored. See docs/kb/sending-limits.md.
| Parameter | In | Type | |
|---|---|---|---|
tenantId * | path | string | |
accountId * | path | string |
200The user's limits.{ "effective": { "maxMessageBytes": 26214400, "maxRecipients": 100, "quotaBytes": 5368709120, "sendPerDay": 1000, "sendPerHour": 50 }, "overrides": { "maxMessageBytes": null, "maxRecipients": null, "quotaBytes": null, "sendPerDay": null, "sendPerHour": 50 }, "plan": "team", "planLimits": { "maxMessageBytes": 26214400, "maxRecipients": 100, "quotaBytes": 5368709120, "sendPerDay": 1000, "sendPerHour": 200 }, "usage": { "sentLastDay": 140, "sentLastHour": 12, "storageBytes": 73400320 } }401The platform API key is missing or wrong, or there is no valid admin panel session.404No such object in this tenant (including objects of other tenants).
/v1/tenants/{tenantId}/accounts/{accountId}/limits
team admins
Set one user's own limits
Replaces all of the user's overrides. A field that is null or left out goes back to the plan's value, so {} clears every override. Overrides may be above or below the plan's values and take effect on the next message sent or stored. Sending limits count recipients, over the last hour and the last 24 hours, through the submission port, webmail, vacation replies and filter redirects. A message size above the server's own limit (CORPSUITE_MAX_MESSAGE_BYTES) has no effect.
| Parameter | In | Type | |
|---|---|---|---|
tenantId * | path | string | |
accountId * | path | string |
200The user's limits after the change.401The platform API key is missing or wrong, or there is no valid admin panel session.404No such object in this tenant (including objects of other tenants).422A value is out of range, or names a domain or account of another tenant. When one setting is refused, `field` names it and `detail` says what is allowed.
/v1/tenants/{tenantId}/accounts/{accountId}/lockout
team admins
Lift a lockout
| Parameter | In | Type | |
|---|---|---|---|
tenantId * | path | string | |
accountId * | path | string |
204Unlocked; the failure count starts again.401The platform API key is missing or wrong, or there is no valid admin panel session.403A team admin asked for something only the platform operator may do, or for another team, or the CSRF token is missing or wrong.404No such object in this tenant (including objects of other tenants).
/v1/tenants/{tenantId}/accounts/{accountId}/login-history
team admins
A user's sign-in attempts, newest first
Successes from the same service and address are kept once every half hour; failures each time. At most 1000.
| Parameter | In | Type | |
|---|---|---|---|
tenantId * | path | string | |
accountId * | path | string | |
days | query | integer | At most what the settings keep. |
200The attempts.401The platform API key is missing or wrong, or there is no valid admin panel session.403A team admin asked for something only the platform operator may do, or for another team, or the CSRF token is missing or wrong.404No such object in this tenant (including objects of other tenants).
/v1/tenants/{tenantId}/accounts/{accountId}/sending-pause
Resume a mailbox's sending after a pause for complaints
Platform administrators only. The account's sendingPausedAt and sendingPausedReason say when and why it paused.
| Parameter | In | Type | |
|---|---|---|---|
tenantId * | path | string | |
accountId * | path | string |
204Resumed.401The platform API key is missing or wrong, or there is no valid admin panel session.403A team admin asked for something only the platform operator may do, or for another team, or the CSRF token is missing or wrong.404No such object in this tenant (including objects of other tenants).
/v1/tenants/{tenantId}/accounts/{accountId}/sessions
team admins
A user's open webmail sessions
| Parameter | In | Type | |
|---|---|---|---|
tenantId * | path | string | |
accountId * | path | string |
200The sessions, last used first.401The platform API key is missing or wrong, or there is no valid admin panel session.403A team admin asked for something only the platform operator may do, or for another team, or the CSRF token is missing or wrong.404No such object in this tenant (including objects of other tenants).
/v1/tenants/{tenantId}/accounts/{accountId}/sessions
team admins
Sign a user out of webmail everywhere
| Parameter | In | Type | |
|---|---|---|---|
tenantId * | path | string | |
accountId * | path | string |
204Signed out.401The platform API key is missing or wrong, or there is no valid admin panel session.403A team admin asked for something only the platform operator may do, or for another team, or the CSRF token is missing or wrong.404No such object in this tenant (including objects of other tenants).
/v1/tenants/{tenantId}/accounts/{accountId}/signup-checks
What the checks found when the account signed up
| Parameter | In | Type | |
|---|---|---|---|
tenantId * | path | string | |
accountId * | path | string |
200The report, or {"checked":false}.401The platform API key is missing or wrong, or there is no valid admin panel session.404No such object in this tenant (including objects of other tenants).
/v1/tenants/{tenantId}/accounts/{accountId}/spam-policy
team admins
A user's own spam threshold and sender lists
| Parameter | In | Type | |
|---|---|---|---|
tenantId * | path | string | |
accountId * | path | string |
200The policy; empty when none is set.401The platform API key is missing or wrong, or there is no valid admin panel session.403A team admin asked for something only the platform operator may do, or for another team, or the CSRF token is missing or wrong.404No such object in this tenant (including objects of other tenants).
/v1/tenants/{tenantId}/accounts/{accountId}/spam-policy
team admins
Replace a user's own spam threshold and sender lists
The same as the team's, except rejectScore, which is set for the whole team (422 here). Users change theirs in webmail too.
| Parameter | In | Type | |
|---|---|---|---|
tenantId * | path | string | |
accountId * | path | string |
{
"allow": [
"newsletter.example"
],
"junkScore": 4
}
200The new policy.400The body is not a valid JSON object, has unknown fields, or is too large.401The platform API key is missing or wrong, or there is no valid admin panel session.403A team admin asked for something only the platform operator may do, or for another team, or the CSRF token is missing or wrong.404No such object in this tenant (including objects of other tenants).422A value is out of range, or names a domain or account of another tenant. When one setting is refused, `field` names it and `detail` says what is allowed.
/v1/tenants/{tenantId}/accounts/{accountId}/suppressions
team admins
Recipients who complained about a mailbox's mail
The mailbox can no longer mail them (submission refuses them with 550 5.7.1).
| Parameter | In | Type | |
|---|---|---|---|
tenantId * | path | string | |
accountId * | path | string |
200The recipients.{ "items": [ { "createdAt": "2026-10-09T03:00:00Z", "reason": "complaint", "recipient": "carol@example.net" } ] }401The platform API key is missing or wrong, or there is no valid admin panel session.403A team admin asked for something only the platform operator may do, or for another team, or the CSRF token is missing or wrong.404No such object in this tenant (including objects of other tenants).
/v1/tenants/{tenantId}/accounts/{accountId}/suppressions/{recipient}
Let a mailbox mail a recipient who complained again
Platform administrators only; the recipient asked for no more of this mail.
| Parameter | In | Type | |
|---|---|---|---|
tenantId * | path | string | |
accountId * | path | string | |
recipient * | path | string |
204Removed.401The platform API key is missing or wrong, or there is no valid admin panel session.403A team admin asked for something only the platform operator may do, or for another team, or the CSRF token is missing or wrong.404No such object in this tenant (including objects of other tenants).
/v1/tenants/{tenantId}/accounts/{accountId}/two-factor
team admins
Turn off an account's two-step sign-in
For a user who lost both their authenticator app and their recovery codes. Confirm who is asking first: afterwards the password alone signs in, in webmail and in mail apps. The secret and recovery codes are deleted; app passwords and sessions stay. Turning it off when it is already off succeeds too. See docs/kb/two-step-sign-in.md.
| Parameter | In | Type | |
|---|---|---|---|
tenantId * | path | string | |
accountId * | path | string |
204Two-step sign-in is off.401The platform API key is missing or wrong, or there is no valid admin panel session.404No such object in this tenant (including objects of other tenants).
/v1/tenants/{tenantId}/accounts/{accountId}/verification
Mark an account verified by hand, or take that away
Adds or removes the manual method. With no methods left the account is unverified. The note says how it was checked.
| Parameter | In | Type | |
|---|---|---|---|
tenantId * | path | string | |
accountId * | path | string |
{
"note": "Called the customer on their number on file.",
"verified": true
}
200The account.401The platform API key is missing or wrong, or there is no valid admin panel session.404No such object in this tenant (including objects of other tenants).422A value is out of range, or names a domain or account of another tenant. When one setting is refused, `field` names it and `detail` says what is allowed.
Admin panel
/v1/session
The signed-in administrator and the session's CSRF token
Needs the session cookie only; the panel calls it on load to recover the CSRF token.
200The session.401The platform API key is missing or wrong, or there is no valid admin panel session.
/v1/session
Sign an administrator in to the admin panel
Checks a SaaS administrator's email and password and starts a session. The body must be JSON, so another site cannot post a sign-in form here. Unknown addresses and wrong passwords get the same 401. Five failures from one address or for one administrator within 15 minutes answer 429 until the window passes. Mail accounts cannot sign in here.
{
"email": "ops@example.com",
"password": "a long passphrase"
}
201Signed in. The response sets the session cookie.400The body is not a valid JSON object, has unknown fields, or is too large.401The platform API key is missing or wrong, or there is no valid admin panel session.415The body is not JSON.429Too many failed sign-ins.
/v1/session
Sign out
Ends the session on the server and clears the cookie. Needs X-CSRF-Token.
204Signed out.401The platform API key is missing or wrong, or there is no valid admin panel session.403The CSRF token is missing or wrong.
/v1/session/options
Which ways of signing in to the panel are open
200Whether Sign in with Zappocity is offered, and whether passwords still work.{ "passwordSignin": true, "sso": true }
/v1/session/sso/callback
Where Zappocity sends the browser back
| Parameter | In | Type | |
|---|---|---|---|
code | query | string | |
state | query | string |
302To /admin, signed in; or to /admin#sso-error=... (expired, unverified, not-staff, unavailable).
/v1/session/sso/start
Sign in to the panel with Zappocity
Sends the browser to Zappocity's sign-in (OpenID Connect, with PKCE); it comes back to /v1/session/sso/callback. The staff member of the same email, once Zappocity has confirmed it, gets a panel session.
302To Zappocity
Aliases
/v1/tenants/{tenantId}/aliases
team admins
List the tenant's aliases
| Parameter | In | Type | |
|---|---|---|---|
tenantId * | path | string |
200Aliases by address.401The platform API key is missing or wrong, or there is no valid admin panel session.404No such object in this tenant (including objects of other tenants).
/v1/tenants/{tenantId}/aliases
team admins
Add an alias address for an account
Mail to the alias is delivered to the account, and the account may send as the alias. The alias's domain and the account must both belong to this tenant (422 otherwise).
| Parameter | In | Type | |
|---|---|---|---|
tenantId * | path | string |
201Created.400The body is not a valid JSON object, has unknown fields, or is too large.401The platform API key is missing or wrong, or there is no valid admin panel session.404No such object in this tenant (including objects of other tenants).409The name or address is taken, the tenant has no free seats, a plan limit would be passed (the body adds limit, used and max), or the object is still in use.422A value is out of range, or names a domain or account of another tenant. When one setting is refused, `field` names it and `detail` says what is allowed.
/v1/tenants/{tenantId}/aliases/{address}
team admins
Remove an alias
| Parameter | In | Type | |
|---|---|---|---|
tenantId * | path | string | |
address * | path | string |
204Removed.401The platform API key is missing or wrong, or there is no valid admin panel session.404No such object in this tenant (including objects of other tenants).
Compliance archive
/v1/tenants/{tenantId}/accounts/{accountId}/hold
team admins
Place a legal hold on one user's archived mail
Keeps the user's archived mail past its retention until lifted. The user is not told. A hold the platform places can only be changed or lifted by the platform.
| Parameter | In | Type | |
|---|---|---|---|
tenantId * | path | string | |
accountId * | path | string |
{
"reason": "Smith v. Example, preserve from 2026-03"
}
200The hold.401The platform API key is missing or wrong, or there is no valid admin panel session.403A team admin asked for something only the platform operator may do, or for another team, or the CSRF token is missing or wrong.404No such object in this tenant (including objects of other tenants).422A value is out of range, or names a domain or account of another tenant. When one setting is refused, `field` names it and `detail` says what is allowed.
/v1/tenants/{tenantId}/accounts/{accountId}/hold
team admins
Lift a user's legal hold
| Parameter | In | Type | |
|---|---|---|---|
tenantId * | path | string | |
accountId * | path | string |
204Lifted (or there was none).401The platform API key is missing or wrong, or there is no valid admin panel session.403A team admin asked for something only the platform operator may do, or for another team, or the CSRF token is missing or wrong.404No such object in this tenant (including objects of other tenants).
/v1/tenants/{tenantId}/archive
team admins
Search the archive
Newest first. Each search is written to the access log with its terms.
| Parameter | In | Type | |
|---|---|---|---|
tenantId * | path | string | |
q | query | string | Words in the subject, addresses or body. |
address | query | string | The user, sender or a recipient. |
direction | query | string | |
from | query | string | Archived on or after: a date (2026-01-31) or an RFC 3339 time. |
to | query | string | Archived before. |
before | query | integer | Page: ids below this one. |
limit | query | integer |
200Matching messages.{ "items": [ { "accountId": "0b6e4c1e-9a0e-4c55-8f0b-3d1f6c1d2e3f", "address": "sam@example.com", "archivedAt": "2026-10-08T12:00:00Z", "direction": "out", "expiresAt": "2033-10-07T12:00:00Z", "from": "sam@example.com", "id": 912, "messageId": "abc@example.com", "rcpts": [ "buyer@example.org" ], "size": 4210, "subject": "Quote", "tenantId": "4f1c2d3e-1111-4a2b-9c3d-5e6f7a8b9c0d" } ] }400The body is not a valid JSON object, has unknown fields, or is too large.401The platform API key is missing or wrong, or there is no valid admin panel session.403A team admin asked for something only the platform operator may do, or for another team, or the CSRF token is missing or wrong.404No such object in this tenant (including objects of other tenants).
/v1/tenants/{tenantId}/archive-access
team admins
Who searched, downloaded or changed the archive
| Parameter | In | Type | |
|---|---|---|---|
tenantId * | path | string | |
before | query | integer | |
limit | query | integer |
200Newest first.{ "items": [ { "action": "search", "at": "2026-10-08T12:00:00Z", "by": "owner@example.com", "detail": "q=\"quote\" address=\"\" direction=\"\" from=\"\" to=\"\" results=3", "id": 41 } ] }400The body is not a valid JSON object, has unknown fields, or is too large.401The platform API key is missing or wrong, or there is no valid admin panel session.403A team admin asked for something only the platform operator may do, or for another team, or the CSRF token is missing or wrong.404No such object in this tenant (including objects of other tenants).
/v1/tenants/{tenantId}/archive-settings
team admins
Archiving settings, size and holds
| Parameter | In | Type | |
|---|---|---|---|
tenantId * | path | string |
200The settings, how much is archived, and the users on hold.{ "accountHolds": [ { "accountId": "0b6e4c1e-9a0e-4c55-8f0b-3d1f6c1d2e3f", "address": "sam@example.com", "by": "owner@example.com", "createdAt": "2026-10-08T12:00:00Z", "platform": false, "reason": "Smith v. Example, preserve from 2026-03" } ], "bytes": 734003200, "messages": 18230, "oldest": "2026-01-04T09:12:00Z", "settings": { "enabled": true, "entitled": true, "hold": false, "holdBy": "", "holdPlatform": false, "holdReason": "", "retentionDays": 2555, "updatedAt": "2026-10-08T12:00:00Z" } }401The platform API key is missing or wrong, or there is no valid admin panel session.403A team admin asked for something only the platform operator may do, or for another team, or the CSRF token is missing or wrong.404No such object in this tenant (including objects of other tenants).
/v1/tenants/{tenantId}/archive-settings
team admins
Turn archiving on or off, set retention, place or lift a team-wide hold
Turning archiving on needs a plan with the archiving entitlement. Archiving starts with the next message; earlier mail is not copied. Lengthening retentionDays also keeps mail already archived longer; shortening it applies only to mail archived from then on. Turning it off stops new copies and keeps the ones there. A team-wide hold (hold, with holdReason) keeps every archived message past its retention until lifted. A hold the platform places can only be changed or lifted by the platform.
| Parameter | In | Type | |
|---|---|---|---|
tenantId * | path | string |
{
"enabled": true,
"retentionDays": 3650
}
200The new settings.401The platform API key is missing or wrong, or there is no valid admin panel session.403A team admin asked for something only the platform operator may do, or for another team, or the CSRF token is missing or wrong.404No such object in this tenant (including objects of other tenants).422A value is out of range, or names a domain or account of another tenant. When one setting is refused, `field` names it and `detail` says what is allowed.
/v1/tenants/{tenantId}/archive/{messageId}/message
team admins
Download an archived message
The message exactly as archived, as an .eml file. Logged.
| Parameter | In | Type | |
|---|---|---|---|
tenantId * | path | string | |
messageId * | path | integer |
200The message.401The platform API key is missing or wrong, or there is no valid admin panel session.403A team admin asked for something only the platform operator may do, or for another team, or the CSRF token is missing or wrong.404No such object in this tenant (including objects of other tenants).
DMARC reports
/v1/tenants/{tenantId}/domains/{domain}/dmarc-reports
team admins
DMARC aggregate reports received for one of the tenant's domains
Reports that receivers sent to dmarc-reports@<domain>, newest first, without their rows. Kept for 180 days. Pass next from a response as before to get the following page.
| Parameter | In | Type | |
|---|---|---|---|
tenantId * | path | string | |
domain * | path | string | |
limit | query | integer | |
before | query | integer | Only reports with a smaller id. |
200Reports.400The body is not a valid JSON object, has unknown fields, or is too large.401The platform API key is missing or wrong, or there is no valid admin panel session.404No such object in this tenant (including objects of other tenants).
/v1/tenants/{tenantId}/domains/{domain}/dmarc-reports/{reportId}
team admins
One DMARC report with its rows
| Parameter | In | Type | |
|---|---|---|---|
tenantId * | path | string | |
domain * | path | string | |
reportId * | path | integer |
200The report; records are sorted by message count, largest first.401The platform API key is missing or wrong, or there is no valid admin panel session.404No such object in this tenant (including objects of other tenants).
/v1/tenants/{tenantId}/domains/{domain}/dmarc-summary
team admins
Sending sources seen in DMARC reports for one of the tenant's domains
Totals of the reports whose period ended in the last days days, by source address, busiest first (at most 500). A message passed when DKIM or SPF passed with alignment; an unknown source with failing messages is either a service you forgot to add to SPF and DKIM, or someone forging your domain.
| Parameter | In | Type | |
|---|---|---|---|
tenantId * | path | string | |
domain * | path | string | |
days | query | integer |
200The summary.400The body is not a valid JSON object, has unknown fields, or is too large.401The platform API key is missing or wrong, or there is no valid admin panel session.404No such object in this tenant (including objects of other tenants).
Domains
/v1/tenants/{tenantId}/domain-transfers
team admins
The team's open domain offers, made and received
| Parameter | In | Type | |
|---|---|---|---|
tenantId * | path | string |
200The offers401The platform API key is missing or wrong, or there is no valid admin panel session.403A team admin asked for something only the platform operator may do, or for another team, or the CSRF token is missing or wrong.404No such object in this tenant (including objects of other tenants).
/v1/tenants/{tenantId}/domain-transfers/{transferId}
team admins
Withdraw (the giving team) or decline (the receiving team) an offer
| Parameter | In | Type | |
|---|---|---|---|
tenantId * | path | string | |
transferId * | path | string |
204Gone.401The platform API key is missing or wrong, or there is no valid admin panel session.403A team admin asked for something only the platform operator may do, or for another team, or the CSRF token is missing or wrong.404No such object in this tenant (including objects of other tenants).
/v1/tenants/{tenantId}/domain-transfers/{transferId}/accept
team admins
Take a domain offered to the team
The checks are made again; the receiving team's domain limit applies.
| Parameter | In | Type | |
|---|---|---|---|
tenantId * | path | string | |
transferId * | path | string |
200The domain401The platform API key is missing or wrong, or there is no valid admin panel session.403A team admin asked for something only the platform operator may do, or for another team, or the CSRF token is missing or wrong.404No such object in this tenant (including objects of other tenants).409A team is not in good standing, the domain is in use, or the team is at its domain limit.
/v1/tenants/{tenantId}/domains
team admins
List the tenant's domains
| Parameter | In | Type | |
|---|---|---|---|
tenantId * | path | string |
200Domains by name.401The platform API key is missing or wrong, or there is no valid admin panel session.404No such object in this tenant (including objects of other tenants).
/v1/tenants/{tenantId}/domains
Add a domain to the tenant without DNS proof
While the platform setting requireDomainProof is on (the default), this answers 409 unless skipProof is true: prove domains with /pending-domains instead. A domain added without proof has verifiedAt null and can be claimed by any tenant that proves it, when no user or alias uses it. A domain belongs to exactly one tenant; adding one that another tenant holds answers 409.
| Parameter | In | Type | |
|---|---|---|---|
tenantId * | path | string |
201Created.400The body is not a valid JSON object, has unknown fields, or is too large.401The platform API key is missing or wrong, or there is no valid admin panel session.404No such object in this tenant (including objects of other tenants).409The name or address is taken, the tenant has no free seats, a plan limit would be passed (the body adds limit, used and max), or the object is still in use.422A value is out of range, or names a domain or account of another tenant. When one setting is refused, `field` names it and `detail` says what is allowed.
/v1/tenants/{tenantId}/domains/{domain}
team admins
Get one of the tenant's domains and its settings
| Parameter | In | Type | |
|---|---|---|---|
tenantId * | path | string | |
domain * | path | string |
200The domain.401The platform API key is missing or wrong, or there is no valid admin panel session.404No such object in this tenant (including objects of other tenants).
/v1/tenants/{tenantId}/domains/{domain}
team admins
Change a domain's settings
Fields left out keep their value. The SPF and DMARC settings change the records GET .../dns lists; publish the new values in DNS for them to take effect. inboundAuth takes effect at once.
| Parameter | In | Type | |
|---|---|---|---|
tenantId * | path | string | |
domain * | path | string |
200The updated domain.400The body is not a valid JSON object, has unknown fields, or is too large.401The platform API key is missing or wrong, or there is no valid admin panel session.404No such object in this tenant (including objects of other tenants).422A value is out of range, or names a domain or account of another tenant. When one setting is refused, `field` names it and `detail` says what is allowed.
/v1/tenants/{tenantId}/domains/{domain}/bimi
team admins
The domain's BIMI logo
Whether the domain has a logo, where this server serves it (logoUrl, and the mark certificate beside it as .pem), the default._bimi TXT record to publish (also in the domain's DNS list), and whether its DMARC policy lets receivers show it (quarantine or reject, for all mail). See docs/kb/bimi.md.
| Parameter | In | Type | |
|---|---|---|---|
tenantId * | path | string | |
domain * | path | string |
200The logo's state.{ "dmarcNote": "", "dmarcReady": true, "domain": "example.com", "hasCertificate": false, "hasLogo": true, "logoUrl": "https://mailo.city/bimi/example.com.svg", "record": { "name": "default._bimi.example.com", "purpose": "BIMI: the brand logo receivers may show next to the domain's mail", "type": "TXT", "value": "v=BIMI1; l=https://mailo.city/bimi/example.com.svg; a=" }, "updatedAt": "2026-10-09T04:00:00Z" }401The platform API key is missing or wrong, or there is no valid admin panel session.403A team admin asked for something only the platform operator may do, or for another team, or the CSRF token is missing or wrong.404No such object in this tenant (including objects of other tenants).
/v1/tenants/{tenantId}/domains/{domain}/bimi
team admins
Set the domain's BIMI logo
The logo must be SVG Tiny Portable/Secure, as receivers require: version 1.2, baseProfile tiny-ps, a title, a square viewBox, no scripts, links, images, animation, styles or outside references, at most 32 KB; the answer lists every problem. The mark certificate (VMC or CMC, PEM with its chain) is optional; Gmail and Apple show logos only with one. Omit certificate to keep the stored one, or send "" to remove it.
| Parameter | In | Type | |
|---|---|---|---|
tenantId * | path | string | |
domain * | path | string |
{
"svg": "\u003csvg xmlns=\"http://www.w3.org/2000/svg\" version=\"1.2\" baseProfile=\"tiny-ps\" viewBox=\"0 0 100 100\"\u003e\u003ctitle\u003eExample\u003c/title\u003e\u003ccircle cx=\"50\" cy=\"50\" r=\"40\" fill=\"#06c\"/\u003e\u003c/svg\u003e"
}
200The logo's state.400The body is not a valid JSON object, has unknown fields, or is too large.401The platform API key is missing or wrong, or there is no valid admin panel session.403A team admin asked for something only the platform operator may do, or for another team, or the CSRF token is missing or wrong.404No such object in this tenant (including objects of other tenants).422A value is out of range, or names a domain or account of another tenant. When one setting is refused, `field` names it and `detail` says what is allowed.
/v1/tenants/{tenantId}/domains/{domain}/bimi
team admins
Remove the domain's BIMI logo
Remove the default._bimi record too; receivers that find it then get no logo.
| Parameter | In | Type | |
|---|---|---|---|
tenantId * | path | string | |
domain * | path | string |
204Removed.401The platform API key is missing or wrong, or there is no valid admin panel session.403A team admin asked for something only the platform operator may do, or for another team, or the CSRF token is missing or wrong.404No such object in this tenant (including objects of other tenants).
/v1/tenants/{tenantId}/domains/{domain}/bimi-check
team admins
Check the published BIMI record
Looks up default._bimi in DNS and compares it with what it should be.
| Parameter | In | Type | |
|---|---|---|---|
tenantId * | path | string | |
domain * | path | string |
200The check.{ "dmarcNote": "", "dmarcReady": true, "expected": "v=BIMI1; l=https://mailo.city/bimi/example.com.svg; a=", "matches": true, "published": "v=BIMI1; l=https://mailo.city/bimi/example.com.svg; a=" }401The platform API key is missing or wrong, or there is no valid admin panel session.403A team admin asked for something only the platform operator may do, or for another team, or the CSRF token is missing or wrong.404No such object in this tenant (including objects of other tenants).409The name or address is taken, the tenant has no free seats, a plan limit would be passed (the body adds limit, used and max), or the object is still in use.
/v1/tenants/{tenantId}/domains/{domain}/dkim
team admins
List the DKIM keys of one of the tenant's domains
Newest first. dnsName and dnsValue are the TXT record that publishes each key; dnsZoneValue is the same value split into quoted strings of at most 255 characters. Private keys are never returned.
| Parameter | In | Type | |
|---|---|---|---|
tenantId * | path | string | |
domain * | path | string |
200Keys.401The platform API key is missing or wrong, or there is no valid admin panel session.404No such object in this tenant (including objects of other tenants).
/v1/tenants/{tenantId}/domains/{domain}/dkim
team admins
Create a DKIM key for a rotation
The new key is pending: publish its TXT record, wait for DNS to update (a day is safe), then activate it. It is active at once only when the domain has no active key. RSA keys are 2048 bits; Ed25519 (RFC 8463) is not yet checked by every receiver, so keep an RSA key active.
| Parameter | In | Type | |
|---|---|---|---|
tenantId * | path | string | |
domain * | path | string |
{
"algorithm": "rsa"
}
201The key.400The body is not a valid JSON object, has unknown fields, or is too large.401The platform API key is missing or wrong, or there is no valid admin panel session.404No such object in this tenant (including objects of other tenants).422A value is out of range, or names a domain or account of another tenant. When one setting is refused, `field` names it and `detail` says what is allowed.
/v1/tenants/{tenantId}/domains/{domain}/dkim/{selector}
team admins
Delete a pending or retired key
The active key cannot be deleted (409); activate another first. Remove the TXT record afterwards.
| Parameter | In | Type | |
|---|---|---|---|
tenantId * | path | string | |
domain * | path | string | |
selector * | path | string |
204Deleted.401The platform API key is missing or wrong, or there is no valid admin panel session.404No such object in this tenant (including objects of other tenants).409The name or address is taken, the tenant has no free seats, a plan limit would be passed (the body adds limit, used and max), or the object is still in use.
/v1/tenants/{tenantId}/domains/{domain}/dkim/{selector}/activate
team admins
Make a key the one that signs
The key that signed before is retired; its record should stay published for a few days so mail in transit still verifies.
| Parameter | In | Type | |
|---|---|---|---|
tenantId * | path | string | |
domain * | path | string | |
selector * | path | string |
200The key, now active.401The platform API key is missing or wrong, or there is no valid admin panel session.404No such object in this tenant (including objects of other tenants).
/v1/tenants/{tenantId}/domains/{domain}/dns
team admins
DNS records the tenant must publish
MX, SPF, DKIM, DMARC and discovery records, and the ownership record (_zappocity-verify.<domain> TXT): to publish and verify for a domain added without proof, or to keep for one proved with it. Records marked optional help but mail works without them. Every DKIM key of the domain is listed: the active one, a pending one waiting to be activated, and retired ones until they are deleted. Unless mtaStsMode is "none", also the mta-sts.<domain> CNAME to this server and the _mta-sts.<domain> TXT record with the policy id.
| Parameter | In | Type | |
|---|---|---|---|
tenantId * | path | string | |
domain * | path | string | |
format | query | string | `json` (the default) lists the records; `zone` returns them as a BIND-style zone file to import at a DNS provider, served as a download named `<domain>.zone`. It has `$ORIGIN` and `$TTL` lines, a comment with each record's purpose, names relative to the domain, and TXT values split into quoted strings of at most 255 characters. It has no SOA or NS records. |
200Records to publish, as JSON or a zone file."; DNS records for example.com, made by Zappocity on 2026-10-07 15:49 UTC.\n$ORIGIN example.com.\n$TTL 3600\n\n; Deliver mail for the domain to this server\n@\t3600\tIN\tMX\t10 mx.zappocity.example.\n"
400The body is not a valid JSON object, has unknown fields, or is too large.401The platform API key is missing or wrong, or there is no valid admin panel session.404No such object in this tenant (including objects of other tenants).
/v1/tenants/{tenantId}/domains/{domain}/dns-check
team admins
Check the domain's DNS records
Looks up every record of the DNS list (/dns) and compares it with what it should be. status is ok, missing, wrong (a record of that kind is there with another value, or there are two SPF or DMARC records), warning (right, but something else may get in the way, such as other MX hosts) or unchecked (the lookup failed). found lists what DNS has. ok is true when every record that is not optional is ok or a warning. Results reflect DNS caches; a change can take a while to show.
| Parameter | In | Type | |
|---|---|---|---|
tenantId * | path | string | |
domain * | path | string |
200The check.{ "checkedAt": "2026-10-09T12:00:00Z", "ok": false, "records": [ { "found": [ "10 mx.zappo.city." ], "name": "example.com", "priority": 10, "purpose": "Deliver mail for the domain to this server", "status": "ok", "type": "MX", "value": "mx.zappo.city." }, { "found": [ "v=DMARC1; p=none" ], "name": "_dmarc.example.com", "purpose": "DMARC", "status": "wrong", "type": "TXT", "value": "v=DMARC1; p=quarantine" }, { "found": [], "name": "_jmap._tcp.example.com", "optional": true, "purpose": "JMAP discovery", "status": "missing", "type": "SRV", "value": "0 1 443 mx.zappo.city." } ] }401The platform API key is missing or wrong, or there is no valid admin panel session.403A team admin asked for something only the platform operator may do, or for another team, or the CSRF token is missing or wrong.404No such object in this tenant (including objects of other tenants).
/v1/tenants/{tenantId}/domains/{domain}/move
Move a domain to another team at once (staff)
As an accepted offer, without asking the receiving team: the same checks, and the receiving team's domain limit.
| Parameter | In | Type | |
|---|---|---|---|
tenantId * | path | string | |
domain * | path | string |
200The domain401The platform API key is missing or wrong, or there is no valid admin panel session.403A team admin asked for something only the platform operator may do, or for another team, or the CSRF token is missing or wrong.404No such object in this tenant (including objects of other tenants).409A team is not in good standing, the domain is in use, or the receiving team is at its domain limit.
/v1/tenants/{tenantId}/domains/{domain}/mta-sts-check
team admins
Check the domain's MTA-STS from outside
Looks up the _mta-sts TXT record and fetches the policy over HTTPS (public addresses and valid certificates only), the way other servers do, and lists what does not match the domain's settings: a missing or old record id, an unreachable policy, another mode, or a policy that does not name this server's mail host.
| Parameter | In | Type | |
|---|---|---|---|
tenantId * | path | string | |
domain * | path | string |
200What was found; an empty `problems` means all is well.{ "fetched": true, "maxAge": 604800, "mode": "enforce", "mx": [ "mx.example.com" ], "problems": [], "recordFound": true, "recordId": "20261008T120000" }401The platform API key is missing or wrong, or there is no valid admin panel session.403A team admin asked for something only the platform operator may do, or for another team, or the CSRF token is missing or wrong.404No such object in this tenant (including objects of other tenants).
/v1/tenants/{tenantId}/domains/{domain}/proved
Mark a domain proved by hand
Records that the operator checked the domain belongs to the tenant, as a DNS TXT proof would: it shows as proved and nobody else can claim it by proving it. For domains added before proofs were required, or with skipProof. Marking a proved domain changes nothing. Platform administrators only.
| Parameter | In | Type | |
|---|---|---|---|
tenantId * | path | string | |
domain * | path | string |
200The domain.{ "name": "zappo.city", "tenantId": "4ee2971f-0fae-4230-8512-ddf1704a769d", "verifiedAt": "2026-10-09T02:10:00Z" }401The platform API key is missing or wrong, or there is no valid admin panel session.403A team admin asked for something only the platform operator may do, or for another team, or the CSRF token is missing or wrong.404No such object in this tenant (including objects of other tenants).
/v1/tenants/{tenantId}/domains/{domain}/transfer
team admins
Offer a domain to another team
The receiving team is named by to, the address of one of its owners or admins (staff may send toTenantId instead); its owners and admins are emailed and accept or decline it within 14 days. Both teams must be in good standing (active, approved, no mailbox with sending paused), and nothing may use the domain: move or remove its mailboxes and aliases first. The domain keeps its settings, DKIM keys, BIMI logo and reports. A new offer replaces an earlier one of the same domain.
| Parameter | In | Type | |
|---|---|---|---|
tenantId * | path | string | |
domain * | path | string |
{
"to": "owner@other.example"
}
201The offer.{ "createdAt": "2026-10-09T12:00:00Z", "domain": "example.com", "expiresAt": "2026-10-23T12:00:00Z", "fromTeam": "Acme", "fromTenantId": "8b30...", "id": "2f4c...", "offeredBy": "boss@acme.example", "toTeam": "Other", "toTenantId": "51aa..." }401The platform API key is missing or wrong, or there is no valid admin panel session.403A team admin asked for something only the platform operator may do, or for another team, or the CSRF token is missing or wrong.404No such object in this tenant (including objects of other tenants).409A team is not in good standing, or the domain still has mailboxes or aliases.422A value is out of range, or names a domain or account of another tenant. When one setting is refused, `field` names it and `detail` says what is allowed.
/v1/tenants/{tenantId}/domains/{domain}/verify
team admins
Prove a domain added without proof
A domain added without proof (skipProof) can be claimed by a team that proves it. Its DNS list has a _zappocity-verify.<domain> TXT record; once it is published, this marks the domain proved. A proved domain is returned as it is.
| Parameter | In | Type | |
|---|---|---|---|
tenantId * | path | string | |
domain * | path | string |
200The domain401The platform API key is missing or wrong, or there is no valid admin panel session.403A team admin asked for something only the platform operator may do, or for another team, or the CSRF token is missing or wrong.404No such object in this tenant (including objects of other tenants).422The record is not there yet.
/v1/tenants/{tenantId}/pending-domains
team admins
Domains waiting for their proof record
| Parameter | In | Type | |
|---|---|---|---|
tenantId * | path | string |
200Pending domains by name.401The platform API key is missing or wrong, or there is no valid admin panel session.403A team admin asked for something only the platform operator may do, or for another team, or the CSRF token is missing or wrong.404No such object in this tenant (including objects of other tenants).
/v1/tenants/{tenantId}/pending-domains
team admins
Ask for a domain that DNS must prove first
Returns the TXT record to publish (recordValue at recordName). Asking again for the same name returns the same record. Nothing about the domain counts as local mail until .../verify finds the record. A name another tenant proved, or a shared domain, answers 409. A domain added without proof (by this tenant or another) can be requested and claimed. A tenant can have 20 waiting.
| Parameter | In | Type | |
|---|---|---|---|
tenantId * | path | string |
201The record to publish.400The body is not a valid JSON object, has unknown fields, or is too large.401The platform API key is missing or wrong, or there is no valid admin panel session.403A team admin asked for something only the platform operator may do, or for another team, or the CSRF token is missing or wrong.404No such object in this tenant (including objects of other tenants).409The name or address is taken, the tenant has no free seats, a plan limit would be passed (the body adds limit, used and max), or the object is still in use.422A value is out of range, or names a domain or account of another tenant. When one setting is refused, `field` names it and `detail` says what is allowed.
/v1/tenants/{tenantId}/pending-domains/{domain}
team admins
Drop a pending domain request
| Parameter | In | Type | |
|---|---|---|---|
tenantId * | path | string | |
domain * | path | string |
204Dropped.401The platform API key is missing or wrong, or there is no valid admin panel session.403A team admin asked for something only the platform operator may do, or for another team, or the CSRF token is missing or wrong.404No such object in this tenant (including objects of other tenants).
/v1/tenants/{tenantId}/pending-domains/{domain}/verify
team admins
Check the proof record and add the domain
Looks up the TXT record. When it is there, the domain is added to the tenant (within the plan's domain limit) with verifiedAt set and a first DKIM key, and every tenant's request for that name is dropped. A domain the tenant already had without proof is marked proved; one another tenant added without proof moves here when no user or alias uses it there, and otherwise answers 409 and waits for the operator. Without the record, 422 says what was not found; try again once DNS has caught up.
| Parameter | In | Type | |
|---|---|---|---|
tenantId * | path | string | |
domain * | path | string |
201The new domain.401The platform API key is missing or wrong, or there is no valid admin panel session.403A team admin asked for something only the platform operator may do, or for another team, or the CSRF token is missing or wrong.404No such object in this tenant (including objects of other tenants).409The name or address is taken, the tenant has no free seats, a plan limit would be passed (the body adds limit, used and max), or the object is still in use.422A value is out of range, or names a domain or account of another tenant. When one setting is refused, `field` names it and `detail` says what is allowed.
Invitations
/v1/invitations
List invitation links
Newest first, the operator's and users' alike, with who made each one, its status and, once used, the new account's address.
| Parameter | In | Type | |
|---|---|---|---|
limit | query | integer |
200The invitations.401The platform API key is missing or wrong, or there is no valid admin panel session.
/v1/invitations
Make invitation links
Makes count links that each sign one person up on plan, even while signups are closed, as long as inviteSignup is on in the platform settings. The person still chooses an address on a shared domain open for signup, and the per-network signup limits still apply. A link works once.
{
"count": 5,
"note": "beta testers",
"plan": "free",
"validDays": 7
}
201The new invitations, each with its link.401The platform API key is missing or wrong, or there is no valid admin panel session.422A value is out of range, or names a domain or account of another tenant. When one setting is refused, `field` names it and `detail` says what is allowed.
/v1/invitations/{code}
Cancel an unused invitation link
| Parameter | In | Type | |
|---|---|---|---|
code * | path | string |
204Cancelled.401The platform API key is missing or wrong, or there is no valid admin panel session.404No such object in this tenant (including objects of other tenants).
/v1/tenants/{tenantId}/accounts/{accountId}/invites
Show a user's invitations to give
How many invitation links the user can still make in webmail, and when more arrive (inviteSettings: invitePerPeriod every invitePeriodDays, up to inviteMaxBalance).
| Parameter | In | Type | |
|---|---|---|---|
tenantId * | path | string | |
accountId * | path | string |
200The allowance.401The platform API key is missing or wrong, or there is no valid admin panel session.404No such object in this tenant (including objects of other tenants).
/v1/tenants/{tenantId}/accounts/{accountId}/invites
Set, add to or reset a user's invitations
Exactly one of set (replace the number), add (add, or take away with a negative number; never below 0) or reset (back to one period's worth, with the drip restarting now). A number above inviteMaxBalance stays; the drip only fills up to it.
| Parameter | In | Type | |
|---|---|---|---|
tenantId * | path | string | |
accountId * | path | string |
200The new allowance.401The platform API key is missing or wrong, or there is no valid admin panel session.404No such object in this tenant (including objects of other tenants).422A value is out of range, or names a domain or account of another tenant. When one setting is refused, `field` names it and `detail` says what is allowed.
/v1/tenants/{tenantId}/team-invites
team admins
The team's invitation links, newest first
| Parameter | In | Type | |
|---|---|---|---|
tenantId * | path | string |
200The links.401The platform API key is missing or wrong, or there is no valid admin panel session.403A team admin asked for something only the platform operator may do, or for another team, or the CSRF token is missing or wrong.404No such object in this tenant (including objects of other tenants).
/v1/tenants/{tenantId}/team-invites
team admins
Make a link to join the team
For maxUses people (default 1, up to 1000), for days (default 14, up to 365). It works while the team's signup is closed, on its signup domain. A team can have 200 working links.
| Parameter | In | Type | |
|---|---|---|---|
tenantId * | path | string |
{
"days": 30,
"maxUses": 5,
"note": "New starters"
}
201The link.401The platform API key is missing or wrong, or there is no valid admin panel session.403A team admin asked for something only the platform operator may do, or for another team, or the CSRF token is missing or wrong.404No such object in this tenant (including objects of other tenants).409The name or address is taken, the tenant has no free seats, a plan limit would be passed (the body adds limit, used and max), or the object is still in use.422A value is out of range, or names a domain or account of another tenant. When one setting is refused, `field` names it and `detail` says what is allowed.
/v1/tenants/{tenantId}/team-invites/{code}
team admins
Stop a link working
| Parameter | In | Type | |
|---|---|---|---|
tenantId * | path | string | |
code * | path | string |
204Revoked.401The platform API key is missing or wrong, or there is no valid admin panel session.403A team admin asked for something only the platform operator may do, or for another team, or the CSRF token is missing or wrong.404No such object in this tenant (including objects of other tenants).
Mail filters
/v1/sieve/validate
team admins
Check a Sieve script
Compiles the script without saving it. Use it for live checks in an editor. An invalid script is still a 200, with valid false and the problem in error.
{
"script": "require \"fileinto\";\nif header :contains \"subject\" \"invoice\" { fileinto \"Invoices\"; }"
}
200Whether the script compiles.400The body is not a valid JSON object, has unknown fields, or is too large.401The platform API key is missing or wrong, or there is no valid admin panel session.
/v1/tenants/{tenantId}/accounts/{accountId}/sieve-scripts
team admins
List an account's filter scripts
| Parameter | In | Type | |
|---|---|---|---|
tenantId * | path | string | |
accountId * | path | string |
200The scripts by name, with their content.401The platform API key is missing or wrong, or there is no valid admin panel session.404No such object in this tenant (including objects of other tenants).
/v1/tenants/{tenantId}/accounts/{accountId}/sieve-scripts
team admins
Add a filter script to an account
An account keeps up to 20 scripts; one may be active. With active: true the new script replaces the active one.
| Parameter | In | Type | |
|---|---|---|---|
tenantId * | path | string | |
accountId * | path | string |
{
"active": true,
"name": "Newsletters",
"script": "require \"fileinto\";\nif exists \"list-id\" { fileinto \"Newsletters\"; }"
}
201The script.400The body is not a valid JSON object, has unknown fields, or is too large.401The platform API key is missing or wrong, or there is no valid admin panel session.404No such object in this tenant (including objects of other tenants).409The name or address is taken, the tenant has no free seats, a plan limit would be passed (the body adds limit, used and max), or the object is still in use.422A value is out of range, or names a domain or account of another tenant. When one setting is refused, `field` names it and `detail` says what is allowed.
/v1/tenants/{tenantId}/accounts/{accountId}/sieve-scripts/{scriptId}
team admins
Get one of an account's filter scripts
| Parameter | In | Type | |
|---|---|---|---|
tenantId * | path | string | |
accountId * | path | string | |
scriptId * | path | integer |
200The script.401The platform API key is missing or wrong, or there is no valid admin panel session.404No such object in this tenant (including objects of other tenants).
/v1/tenants/{tenantId}/accounts/{accountId}/sieve-scripts/{scriptId}
team admins
Rename, replace, turn on or turn off a filter script
active: true makes this the account's only active script; active: false turns the account's own filters off.
| Parameter | In | Type | |
|---|---|---|---|
tenantId * | path | string | |
accountId * | path | string | |
scriptId * | path | integer |
200The script.400The body is not a valid JSON object, has unknown fields, or is too large.401The platform API key is missing or wrong, or there is no valid admin panel session.404No such object in this tenant (including objects of other tenants).409The name or address is taken, the tenant has no free seats, a plan limit would be passed (the body adds limit, used and max), or the object is still in use.422A value is out of range, or names a domain or account of another tenant. When one setting is refused, `field` names it and `detail` says what is allowed.
/v1/tenants/{tenantId}/accounts/{accountId}/sieve-scripts/{scriptId}
team admins
Delete a filter script
The active script cannot be deleted (409); turn it off first.
| Parameter | In | Type | |
|---|---|---|---|
tenantId * | path | string | |
accountId * | path | string | |
scriptId * | path | integer |
204Deleted.401The platform API key is missing or wrong, or there is no valid admin panel session.404No such object in this tenant (including objects of other tenants).409The name or address is taken, the tenant has no free seats, a plan limit would be passed (the body adds limit, used and max), or the object is still in use.
/v1/tenants/{tenantId}/domains/{domain}/sieve
team admins
Get the domain rules
Rules for mail addressed to the domain, run after the tenant rules and before the account's own filters.
| Parameter | In | Type | |
|---|---|---|---|
tenantId * | path | string | |
domain * | path | string |
200The rules; an empty script means none.401The platform API key is missing or wrong, or there is no valid admin panel session.404No such object in this tenant (including objects of other tenants).
/v1/tenants/{tenantId}/domains/{domain}/sieve
team admins
Replace the domain rules
The script is checked before it is saved; one that does not compile is refused with a 422 whose detail gives the problem and where it is. Send {"script": ""} to remove the rules.
| Parameter | In | Type | |
|---|---|---|---|
tenantId * | path | string | |
domain * | path | string |
200The rules as saved.400The body is not a valid JSON object, has unknown fields, or is too large.401The platform API key is missing or wrong, or there is no valid admin panel session.404No such object in this tenant (including objects of other tenants).422A value is out of range, or names a domain or account of another tenant. When one setting is refused, `field` names it and `detail` says what is allowed.
/v1/tenants/{tenantId}/sieve
team admins
Get the tenant rules
Rules that run before every account's own filters in the tenant, ahead of domain rules.
| Parameter | In | Type | |
|---|---|---|---|
tenantId * | path | string |
200The rules; an empty script means none.401The platform API key is missing or wrong, or there is no valid admin panel session.404No such object in this tenant (including objects of other tenants).
/v1/tenants/{tenantId}/sieve
team admins
Replace the tenant rules
The script is checked before it is saved; one that does not compile is refused with a 422 whose detail gives the problem and where it is. Send {"script": ""} to remove the rules.
| Parameter | In | Type | |
|---|---|---|---|
tenantId * | path | string |
200The rules as saved.400The body is not a valid JSON object, has unknown fields, or is too large.401The platform API key is missing or wrong, or there is no valid admin panel session.404No such object in this tenant (including objects of other tenants).422A value is out of range, or names a domain or account of another tenant. When one setting is refused, `field` names it and `detail` says what is allowed.
Outbound mail
/v1/tenants/{tenantId}/queue
team admins
Mail to other domains still in the queue
One item per recipient of each message that still has a recipient waiting. Delivered and failed recipients are listed until the whole message is finished, then removed. Temporary failures are retried after 1, 5, 15 and 30 minutes, 1 and 2 hours, then every 4 hours, for up to five days; the sender then gets a failure notice.
| Parameter | In | Type | |
|---|---|---|---|
tenantId * | path | string |
200Queue entries, oldest message first (at most 1000).401The platform API key is missing or wrong, or there is no valid admin panel session.404No such object in this tenant (including objects of other tenants).
Plans
/v1/entitlements
team admins
The entitlements a plan can switch on
enforced says whether the server checks the entitlement today. The others can be set on plans now and take effect when their feature ships.
200The catalogue.401The platform API key is missing or wrong, or there is no valid admin panel session.
/v1/plans
List plans
200Plans by sort order.401The platform API key is missing or wrong, or there is no valid admin panel session.
/v1/plans
Add a plan
slug, name, kind and every limit are required. Free and solo plans have exactly one user.
201Created.400The body is not a valid JSON object, has unknown fields, or is too large.401The platform API key is missing or wrong, or there is no valid admin panel session.409The name or address is taken, the tenant has no free seats, a plan limit would be passed (the body adds limit, used and max), or the object is still in use.422A value is out of range, or names a domain or account of another tenant. When one setting is refused, `field` names it and `detail` says what is allowed.
/v1/plans/{plan}
Get a plan
| Parameter | In | Type | |
|---|---|---|---|
plan * | path | string | The plan's slug. |
200The plan.401The platform API key is missing or wrong, or there is no valid admin panel session.404No such object in this tenant (including objects of other tenants).
/v1/plans/{plan}
Change a plan's name, limits, entitlements or availability
Fields left out keep their value; inside limits, so do limits left out. entitlements replaces the whole list. slug and kind cannot change. New limits apply to every tenant on the plan at once; lowering one removes nothing, it only stops further growth. active: false stops the plan being given to new tenants; tenants already on it stay.
| Parameter | In | Type | |
|---|---|---|---|
plan * | path | string | The plan's slug. |
200The updated plan.400The body is not a valid JSON object, has unknown fields, or is too large.401The platform API key is missing or wrong, or there is no valid admin panel session.404No such object in this tenant (including objects of other tenants).422A value is out of range, or names a domain or account of another tenant. When one setting is refused, `field` names it and `detail` says what is allowed.
/v1/plans/{plan}
Remove a plan no tenant is on
| Parameter | In | Type | |
|---|---|---|---|
plan * | path | string | The plan's slug. |
204Removed.401The platform API key is missing or wrong, or there is no valid admin panel session.404No such object in this tenant (including objects of other tenants).409The name or address is taken, the tenant has no free seats, a plan limit would be passed (the body adds limit, used and max), or the object is still in use.
Platform
/v1/abuse-settings
Captcha and signup address checks
Secrets show only as captchaSecretSet and ipqsKeySet.
200The settings.401The platform API key is missing or wrong, or there is no valid admin panel session.
/v1/abuse-settings
Change the captcha and signup address checks
captchaProvider: off, pow (built in, a proof of work with no third party), turnstile or hcaptcha (with captchaSiteKey and captchaSecret), shown on signup, team join pages, password reset requests and webmail sign-in as chosen. The signup and join pages, and webmail, allow the provider's scripts and frames in their policy only while it is chosen. Signup addresses (not invitations) are checked with IPQualityScore (ipqsKey, a fraud score limit, and Tor, VPN and proxy choices), DNS blocklists (dnsblZones) and StopForumSpam; a failed check holds the signup for approval (action review) or refuses it (block). Checks that cannot run let the signup through. ipnetdbPath is an IPNetDB .mmdb file on the server for network data. Empty secrets remove them.
{
"action": "review",
"captchaProvider": "pow",
"ipqsEnabled": true,
"ipqsKey": "...",
"ipqsMaxScore": 85
}
200The new settings.401The platform API key is missing or wrong, or there is no valid admin panel session.422A value is out of range, or names a domain or account of another tenant. When one setting is refused, `field` names it and `detail` says what is allowed.
/v1/abuse-settings/test
Run the signup checks on an address
200What each check found and the verdict.{ "ip": "203.0.113.5", "ipqs": { "asn": 64500, "country": "NL", "fraudScore": 97, "isp": "Hosting", "proxy": true, "tor": false, "vpn": false }, "network": { "asn": 64500, "entity": "Example Hosting" }, "reasons": [ "IPQualityScore fraud score 97 (limit 85)" ], "verdict": "review" }401The platform API key is missing or wrong, or there is no valid admin panel session.422A value is out of range, or names a domain or account of another tenant. When one setting is refused, `field` names it and `detail` says what is allowed.
/v1/complaints
Complaints from providers' feedback loops, and complaint rates
The newest complaints (recipient is empty when the provider hides who complained), and every mailbox with complaints in the last 30 days with its rate per thousand recipients mailed on other servers, highest first; sendingPausedAt is set while its sending is paused.
| Parameter | In | Type | |
|---|---|---|---|
limit | query | integer |
200Complaints and rates.{ "items": [ { "accountId": "0b6e4c1e-9a0e-4c55-8f0b-3d1f6c1d2e3f", "address": "bob@acme.test", "feedbackType": "abuse", "id": 7, "messageId": "m1@acme.test", "receivedAt": "2026-10-09T03:00:00Z", "recipient": "carol@example.net", "reporter": "Yahoo!-Mail-Feedback/2.0", "tenantId": "4ee2971f-0fae-4230-8512-ddf1704a769d" } ], "rates": [ { "accountId": "0b6e4c1e-9a0e-4c55-8f0b-3d1f6c1d2e3f", "address": "bob@acme.test", "complaints": 4, "permille": 4.4, "sendingPausedAt": "2026-10-09T03:00:01Z", "sent": 900, "tenantId": "4ee2971f-0fae-4230-8512-ddf1704a769d" } ] }401The platform API key is missing or wrong, or there is no valid admin panel session.
/v1/domain-blocks
The platform's domain blocks
Mail from other servers is refused, or filed in Junk, when a block matches: kind domain matches the sender's domain (envelope and From) and its subdomains, mx the mail servers of the sender's domain, and named any domain the message names (From, Sender, Reply-To and links in its text). The platform's, the team's and the user's blocks all apply. See docs/kb/domain-blocking.md.
200The blocks.{ "items": [ { "action": "reject", "createdAt": "2026-10-09T12:00:00Z", "createdBy": "admin@example.com", "id": 7, "kind": "mx", "note": "Snowshoe spam", "value": "bulk-mailer.example" } ] }401The platform API key is missing or wrong, or there is no valid admin panel session.403A team admin asked for something only the platform operator may do, or for another team, or the CSRF token is missing or wrong.404No such object in this tenant (including objects of other tenants).
/v1/domain-blocks
Block a domain
{
"action": "reject",
"kind": "named",
"note": "Links in the invoice scam",
"value": "phish.example"
}
201The block.401The platform API key is missing or wrong, or there is no valid admin panel session.403A team admin asked for something only the platform operator may do, or for another team, or the CSRF token is missing or wrong.404No such object in this tenant (including objects of other tenants).409The name or address is taken, the tenant has no free seats, a plan limit would be passed (the body adds limit, used and max), or the object is still in use.422A value is out of range, or names a domain or account of another tenant. When one setting is refused, `field` names it and `detail` says what is allowed.
/v1/domain-blocks/{blockId}
Remove a domain block
| Parameter | In | Type | |
|---|---|---|---|
blockId * | path | integer |
204Removed.401The platform API key is missing or wrong, or there is no valid admin panel session.403A team admin asked for something only the platform operator may do, or for another team, or the CSRF token is missing or wrong.404No such object in this tenant (including objects of other tenants).
/v1/feedback-settings
The abuse feedback loop's settings
address is where mailbox providers' feedback loops send complaint reports (ARF, RFC 5965); empty receives none. Reports must pass DMARC, and count only for mail this platform sent (every message to another server carries a signed X-Mailocity-Sender tag). A mailbox's sending to other servers pauses above pausePermille complaints per thousand recipients over 30 days, once it mailed at least minSent (0 never pauses). See docs/kb/feedback-loops.md.
200The settings.{ "address": "fbl@mailo.city", "minSent": 200, "pausePermille": 3 }401The platform API key is missing or wrong, or there is no valid admin panel session.
/v1/feedback-settings
Change the feedback loop's settings
{
"address": "fbl@mailo.city",
"pausePermille": 3
}
200The settings.401The platform API key is missing or wrong, or there is no valid admin panel session.403A team admin asked for something only the platform operator may do, or for another team, or the CSRF token is missing or wrong.422A value is out of range, or names a domain or account of another tenant. When one setting is refused, `field` names it and `detail` says what is allowed.
/v1/greylist-settings
Greylisting times and the platform's IP allowlist
Greylisting holds a first attempt for delaySeconds, counts a retry up to retryHours later, and remembers a passed network, sender and recipient for passDays. Networks on ipAllowlist (addresses or CIDR) skip greylisting and spam scoring for every recipient (viruses are still refused), and their signups skip the reputation checks. Greylisting is switched on per domain. See docs/kb/greylisting.md.
200The settings.{ "delaySeconds": 300, "ipAllowlist": [ "198.51.100.0/24", "2001:db8:1::/48" ], "passDays": 36, "retryHours": 24 }401The platform API key is missing or wrong, or there is no valid admin panel session.
/v1/greylist-settings
Change greylisting times and the platform's IP allowlist
{
"ipAllowlist": [
"192.0.2.10",
"198.51.100.0/24"
]
}
200The settings.401The platform API key is missing or wrong, or there is no valid admin panel session.403A team admin asked for something only the platform operator may do, or for another team, or the CSRF token is missing or wrong.422A value is out of range, or names a domain or account of another tenant. When one setting is refused, `field` names it and `detail` says what is allowed.
/v1/legal-exports
The audit log of legal exports, newest first
200The exports.{ "items": [ { "accountAddress": "alice@acme.com", "bytes": 182211, "caseReference": "Subpoena 2026-117", "createdAt": "2026-10-08T15:00:00Z", "id": "6c1d...", "note": "", "requestedBy": "ops@example.com", "scope": { "from": "2026-01-01T00:00:00Z", "include": [ "account", "messages" ], "to": "2026-10-01T00:00:00Z" }, "sha256": "9f2c..." } ] }401The platform API key is missing or wrong, or there is no valid admin panel session.
/v1/mail-log
Search the mail log
Lines as Exim writes them, newest first: arrivals (<=), deliveries (=>), deferrals (==), failures (**), Completed, refusals (rejected, also in the reject log) and internal errors (the panic log). Every line of one message shares its id, so searching for the id shows its whole story. Lines are kept for the days the mail log settings say; the same lines are written to mainlog, rejectlog and paniclog in CORPSUITE_LOG_DIR.
| Parameter | In | Type | |
|---|---|---|---|
q | query | string | A message id, an address, an IP, or text in the line. |
log | query | string | |
since | query | string | |
until | query | string | |
before | query | integer | Page by line id (the previous answer's next). |
limit | query | integer |
200The lines.{ "items": [ { "at": "2026-10-09T12:00:01Z", "id": 912, "line": "2026-10-09 12:00:01 1u2v3w-00AbCd-Xy =\u003e bob@example.net H=mx.example.net R=dnslookup T=remote_smtp", "log": "main", "messageId": "1u2v3w-00AbCd-Xy" }, { "at": "2026-10-09T12:00:00Z", "id": 911, "line": "2026-10-09 12:00:00 1u2v3w-00AbCd-Xy \u003c= alice@example.com H=client.example [192.0.2.1] P=esmtpsa A=plain:alice@example.com S=1234 id=\u003cx@y\u003e", "log": "main", "messageId": "1u2v3w-00AbCd-Xy" } ], "next": 911 }400The body is not a valid JSON object, has unknown fields, or is too large.401The platform API key is missing or wrong, or there is no valid admin panel session.
/v1/mail-log-settings
How long the mail log is kept for searching
200The settings.{ "days": 30 }401The platform API key is missing or wrong, or there is no valid admin panel session.
/v1/mail-log-settings
Change how long the mail log is kept
The log files are not touched; rotate them with logrotate.
{
"days": 90
}
200The settings.401The platform API key is missing or wrong, or there is no valid admin panel session.403A team admin asked for something only the platform operator may do, or for another team, or the CSRF token is missing or wrong.422A value is out of range, or names a domain or account of another tenant. When one setting is refused, `field` names it and `detail` says what is allowed.
/v1/mta-sts-settings
MTA-STS defaults and how sent mail follows other domains' policies
200The settings.{ "defaultMaxAge": 604800, "defaultMode": "testing", "outbound": "follow", "updatedAt": "2026-10-08T12:00:00Z" }401The platform API key is missing or wrong, or there is no valid admin panel session.
/v1/mta-sts-settings
Change MTA-STS defaults and the handling of sent mail
defaultMode (none, testing, enforce) and defaultMaxAge are what new domains and shared domains start with; existing domains keep theirs (change each with PATCH on the domain). outbound: follow does what each receiving domain's policy asks (RFC 8461); testing delivers anyway and logs problems; off ignores policies.
{
"defaultMode": "enforce"
}
200The new settings.401The platform API key is missing or wrong, or there is no valid admin panel session.422A value is out of range, or names a domain or account of another tenant. When one setting is refused, `field` names it and `detail` says what is allowed.
/v1/notice-mail
How Mailocity sends its notices
notice (the default) sends from noticeFrom in the settings as before; mailbox sends as a mailbox here, through the same checks and limits as its user's mail; smtp sends by SMTP submission; jmap sends as a mailbox over JMAP (RFC 8621), here or on another server, so each notice is also kept in that mailbox's Sent folder. The SMTP password and the JMAP token are never returned.
200The setting.{ "accountId": "0b6e4c1e-9a0e-4c55-8f0b-3d1f6c1d2e3f", "address": "noreply@mailo.city", "hasJmapToken": true, "hasSmtpPassword": false, "jmapUrl": "", "mode": "jmap", "smtpHost": "", "smtpPort": 587, "smtpSecurity": "starttls", "smtpUsername": "" }401The platform API key is missing or wrong, or there is no valid admin panel session.
/v1/notice-mail
Change how Mailocity sends its notices
200The setting.401The platform API key is missing or wrong, or there is no valid admin panel session.403A team admin asked for something only the platform operator may do, or for another team, or the CSRF token is missing or wrong.422A value is out of range, or names a domain or account of another tenant. When one setting is refused, `field` names it and `detail` says what is allowed.
/v1/notice-mail/test
Send a test notice
200Whether it went, and the error if not.{ "error": "signing in to mailo.city: 535 authentication failed", "ok": false }401The platform API key is missing or wrong, or there is no valid admin panel session.422A value is out of range, or names a domain or account of another tenant. When one setting is refused, `field` names it and `detail` says what is allowed.
/v1/security-settings
Lockout, sign-in history and alert settings
200The settings.401The platform API key is missing or wrong, or there is no valid admin panel session.
/v1/security-settings
Change lockout, sign-in history and alert settings
Every password check (webmail, IMAP, SMTP, ManageSieve, JMAP) counts: lockoutThreshold wrong passwords within lockoutWindowMinutes lock the account for lockoutMinutes (0 turns lockouts off). A locked account is refused even with the right password, with the same answer as a throttled one. Sign-in history is kept for historyDays. With alertDefault, users are emailed about lockouts, repeated wrong passwords and sign-ins from new addresses unless they turn it off. Defaults: 10, 15, 15, 90, true.
{
"historyDays": 180,
"lockoutMinutes": 30,
"lockoutThreshold": 8
}
200The new settings.401The platform API key is missing or wrong, or there is no valid admin panel session.422A value is out of range, or names a domain or account of another tenant. When one setting is refused, `field` names it and `detail` says what is allowed.
/v1/settings
Platform-wide signup and webmail settings
200The settings.401The platform API key is missing or wrong, or there is no valid admin panel session.
/v1/settings
Change signups, their limits and the webmail host
Fields left out keep their value. reservedLocalParts replaces the whole list.
200The updated settings.400The body is not a valid JSON object, has unknown fields, or is too large.401The platform API key is missing or wrong, or there is no valid admin panel session.422A value is out of range, or names a domain or account of another tenant. When one setting is refused, `field` names it and `detail` says what is allowed.
/v1/spam-reports
Spam and phishing reports from every tenant, newest first
Act on one through its tenant's /spam-reports/{reportId} routes.
| Parameter | In | Type | |
|---|---|---|---|
status | query | string | |
limit | query | integer |
200The reports.400The body is not a valid JSON object, has unknown fields, or is too large.401The platform API key is missing or wrong, or there is no valid admin panel session.
/v1/spam-settings
The Rspamd connection and the platform's spam thresholds
The password is never returned; passwordSet says whether one is stored.
200The settings.401The platform API key is missing or wrong, or there is no valid admin panel session.
/v1/spam-settings
Change the Rspamd connection or the platform's thresholds
With enabled, mail from other servers is scored by Rspamd at url (its normal or controller worker) during the SMTP transaction. Mail at or above a recipient's junk score is filed in Junk; at or above the reject score it is refused for that recipient, and the message is refused when every recipient refuses it. A virus found by Rspamd's antivirus module (symbols with VIRUS in the name) is always refused. password is the controller password used for learning; it is sealed with the server's key, and an empty string removes it. With failOpen, mail is delivered unscanned while Rspamd cannot be reached; otherwise senders are asked to try again later. With learn, messages users move into Junk are learned as spam and out of Junk (except to Trash) as ham, from webmail and from mail apps alike.
{
"enabled": true,
"junkScore": 6,
"password": "controller password",
"rejectScore": 15,
"url": "http://127.0.0.1:11334"
}
200The new settings.400The body is not a valid JSON object, has unknown fields, or is too large.401The platform API key is missing or wrong, or there is no valid admin panel session.422A value is out of range, or names a domain or account of another tenant. When one setting is refused, `field` names it and `detail` says what is allowed.
/v1/spam-settings/test
Check the saved Rspamd connection
Asks Rspamd's /ping, and with a password its controller's /stat, whether or not scanning is enabled. Save new settings first.
200What was found; `error` says what failed.{ "millis": 3, "passwordOk": true, "reachable": true, "version": "3.9.1" }401The platform API key is missing or wrong, or there is no valid admin panel session.
/v1/sso-settings
Single sign-on through Zappocity
Whether webmail, the team manager and the admin panel sign in through Zappocity, its address and client, and whether the password sign-ins still work. The client secret is never returned.
200The settings.{ "adminPasswordSignin": true, "clientId": "mailocity", "enabled": true, "hasClientSecret": true, "issuer": "https://zappo.city", "webmailPasswordSignin": true }401The platform API key is missing or wrong, or there is no valid admin panel session.
/v1/sso-settings
Change single sign-on, or turn the password sign-ins off
A password sign-in can be turned off only while single sign-on is on and set up, so nobody is locked out; the command line on the server always works. Mail apps are not affected: they keep using passwords and app passwords.
{
"webmailPasswordSignin": false
}
200The settings.401The platform API key is missing or wrong, or there is no valid admin panel session.403A team admin asked for something only the platform operator may do, or for another team, or the CSRF token is missing or wrong.422A value is out of range, or names a domain or account of another tenant. When one setting is refused, `field` names it and `detail` says what is allowed.
/v1/sso-settings/connect
Connect to the Zappocity on this server
Registers Mailocity as a client of the Zappocity running in the same process (or gives it a new secret and the current return addresses), gives every mailbox and staff member a Zappocity account with the same email and password, and turns single sign-on on.
200The settings.401The platform API key is missing or wrong, or there is no valid admin panel session.403A team admin asked for something only the platform operator may do, or for another team, or the CSRF token is missing or wrong.503Zappocity is not in this process.
/v1/staff
The panel's administrators and their permissions
200Staff, and the permission catalogue.{ "items": [ { "disabled": false, "email": "help@example.com", "id": "2b1f...", "name": "Helper", "permissions": [ "read", "tenants", "reports" ] } ], "permissions": [ "read", "tenants", "approvals", "reports", "plans", "settings", "legal", "staff", "impersonate" ] }401The platform API key is missing or wrong, or there is no valid admin panel session.403A team admin asked for something only the platform operator may do, or for another team, or the CSRF token is missing or wrong.
/v1/staff
Add a panel administrator
{
"email": "help@example.com",
"name": "Helper",
"password": "a long password",
"permissions": [
"read",
"tenants",
"reports",
"impersonate"
]
}
201Added.401The platform API key is missing or wrong, or there is no valid admin panel session.403A team admin asked for something only the platform operator may do, or for another team, or the CSRF token is missing or wrong.409The name or address is taken, the tenant has no free seats, a plan limit would be passed (the body adds limit, used and max), or the object is still in use.422A value is out of range, or names a domain or account of another tenant. When one setting is refused, `field` names it and `detail` says what is allowed.
/v1/staff/{staffId}
Change a staff member's name, permissions, password or status
Changing permissions, disabling or a new password signs them out. 409 when nobody would be left able to manage staff.
| Parameter | In | Type | |
|---|---|---|---|
staffId * | path | string |
200Changed.401The platform API key is missing or wrong, or there is no valid admin panel session.403A team admin asked for something only the platform operator may do, or for another team, or the CSRF token is missing or wrong.404No such object in this tenant (including objects of other tenants).409The name or address is taken, the tenant has no free seats, a plan limit would be passed (the body adds limit, used and max), or the object is still in use.422A value is out of range, or names a domain or account of another tenant. When one setting is refused, `field` names it and `detail` says what is allowed.
/v1/tenants/{tenantId}/complaints
team admins
A team's complaints and complaint rates
As /v1/complaints, for one team; its admins see their own.
| Parameter | In | Type | |
|---|---|---|---|
tenantId * | path | string | |
limit | query | integer |
200Complaints and rates.401The platform API key is missing or wrong, or there is no valid admin panel session.403A team admin asked for something only the platform operator may do, or for another team, or the CSRF token is missing or wrong.404No such object in this tenant (including objects of other tenants).
/v1/tenants/{tenantId}/domain-blocks
team admins
A team's domain blocks
Mail from other servers is refused, or filed in Junk, when a block matches: kind domain matches the sender's domain (envelope and From) and its subdomains, mx the mail servers of the sender's domain, and named any domain the message names (From, Sender, Reply-To and links in its text). The platform's, the team's and the user's blocks all apply. See docs/kb/domain-blocking.md.
| Parameter | In | Type | |
|---|---|---|---|
tenantId * | path | string |
200The blocks.{ "items": [ { "action": "reject", "createdAt": "2026-10-09T12:00:00Z", "createdBy": "admin@example.com", "id": 7, "kind": "mx", "note": "Snowshoe spam", "value": "bulk-mailer.example" } ] }401The platform API key is missing or wrong, or there is no valid admin panel session.403A team admin asked for something only the platform operator may do, or for another team, or the CSRF token is missing or wrong.404No such object in this tenant (including objects of other tenants).
/v1/tenants/{tenantId}/domain-blocks
team admins
Block a domain
| Parameter | In | Type | |
|---|---|---|---|
tenantId * | path | string |
{
"action": "reject",
"kind": "named",
"note": "Links in the invoice scam",
"value": "phish.example"
}
201The block.401The platform API key is missing or wrong, or there is no valid admin panel session.403A team admin asked for something only the platform operator may do, or for another team, or the CSRF token is missing or wrong.404No such object in this tenant (including objects of other tenants).409The name or address is taken, the tenant has no free seats, a plan limit would be passed (the body adds limit, used and max), or the object is still in use.422A value is out of range, or names a domain or account of another tenant. When one setting is refused, `field` names it and `detail` says what is allowed.
/v1/tenants/{tenantId}/domain-blocks/{blockId}
team admins
Remove a domain block
| Parameter | In | Type | |
|---|---|---|---|
tenantId * | path | string | |
blockId * | path | integer |
204Removed.401The platform API key is missing or wrong, or there is no valid admin panel session.403A team admin asked for something only the platform operator may do, or for another team, or the CSRF token is missing or wrong.404No such object in this tenant (including objects of other tenants).
/v1/tenants/{tenantId}/ip-allowlist
team admins
A team's IP allowlist
Networks whose mail to the team's mailboxes skips greylisting and spam scoring (viruses are still refused), such as a partner's or a scanner's sending servers.
| Parameter | In | Type | |
|---|---|---|---|
tenantId * | path | string |
200The networks.{ "items": [ "203.0.113.10/32", "198.51.100.0/24" ] }401The platform API key is missing or wrong, or there is no valid admin panel session.403A team admin asked for something only the platform operator may do, or for another team, or the CSRF token is missing or wrong.404No such object in this tenant (including objects of other tenants).
/v1/tenants/{tenantId}/ip-allowlist
team admins
Replace a team's IP allowlist
| Parameter | In | Type | |
|---|---|---|---|
tenantId * | path | string |
{
"items": [
"203.0.113.10",
"198.51.100.0/24"
]
}
200The networks401The platform API key is missing or wrong, or there is no valid admin panel session.403A team admin asked for something only the platform operator may do, or for another team, or the CSRF token is missing or wrong.404No such object in this tenant (including objects of other tenants).422A value is out of range, or names a domain or account of another tenant. When one setting is refused, `field` names it and `detail` says what is allowed.
/v1/tenants/{tenantId}/limits
A team's limits
The plan's limits, the team's own (null: the plan's), and the result. -1 is unlimited. Each user's limits come from the user's own, then the team's, then the plan's; the team's user cap is its seatLimit.
| Parameter | In | Type | |
|---|---|---|---|
tenantId * | path | string |
200The limits.{ "effective": { "maxAliases": 100, "maxAliasesPerUser": 10, "maxDomains": 10, "maxMessageBytes": 26214400, "maxRecipients": 100, "maxSharedAliasesPerUser": 10, "maxUsers": 50, "quotaBytes": -1, "sendPerDay": -1, "sendPerHour": -1 }, "overrides": { "maxAliases": null, "maxAliasesPerUser": null, "maxDomains": null, "maxMessageBytes": null, "maxRecipients": null, "maxSharedAliasesPerUser": null, "quotaBytes": -1, "sendPerDay": -1, "sendPerHour": -1 }, "plan": "team", "planLimits": { "maxAliases": 100, "maxAliasesPerUser": 10, "maxDomains": 10, "maxMessageBytes": 26214400, "maxRecipients": 100, "maxSharedAliasesPerUser": 10, "maxUsers": 50, "quotaBytes": 32212254720, "sendPerDay": 1000, "sendPerHour": 200 }, "seatLimit": -1 }401The platform API key is missing or wrong, or there is no valid admin panel session.403A team admin asked for something only the platform operator may do, or for another team, or the CSRF token is missing or wrong.404No such object in this tenant (including objects of other tenants).
/v1/tenants/{tenantId}/limits
Replace a team's own limits
Every field is the team's own value, -1 for unlimited, or null (or left out) for the plan's.
| Parameter | In | Type | |
|---|---|---|---|
tenantId * | path | string |
{
"quotaBytes": -1,
"sendPerDay": -1,
"sendPerHour": -1
}
200The limits401The platform API key is missing or wrong, or there is no valid admin panel session.403A team admin asked for something only the platform operator may do, or for another team, or the CSRF token is missing or wrong.404No such object in this tenant (including objects of other tenants).422A value is out of range, or names a domain or account of another tenant. When one setting is refused, `field` names it and `detail` says what is allowed.
/v1/tenants/{tenantId}/mail-log
team admins
Search a team's mail log
Lines about the team's mailboxes, and every line of the messages they concern; the panic log is the operator's only.
| Parameter | In | Type | |
|---|---|---|---|
tenantId * | path | string | |
q | query | string | A message id, an address, an IP, or text in the line. |
log | query | string | |
since | query | string | |
until | query | string | |
before | query | integer | Page by line id (the previous answer's next). |
limit | query | integer |
200The lines.400The body is not a valid JSON object, has unknown fields, or is too large.401The platform API key is missing or wrong, or there is no valid admin panel session.403A team admin asked for something only the platform operator may do, or for another team, or the CSRF token is missing or wrong.404No such object in this tenant (including objects of other tenants).
Support
/v1/tenants/{tenantId}/accounts/{accountId}/notes
team admins
Internal notes on a user
Team admins see team notes; staff see staff notes too. Users never see notes.
| Parameter | In | Type | |
|---|---|---|---|
tenantId * | path | string | |
accountId * | path | string |
200Newest first.{ "items": [ { "author": "help@example.com", "body": "Sent spam on 8 Oct; watch.", "createdAt": "2026-10-08T12:00:00Z", "id": 3, "scope": "staff" } ] }401The platform API key is missing or wrong, or there is no valid admin panel session.403A team admin asked for something only the platform operator may do, or for another team, or the CSRF token is missing or wrong.404No such object in this tenant (including objects of other tenants).
/v1/tenants/{tenantId}/accounts/{accountId}/notes
team admins
Add an internal note on a user
| Parameter | In | Type | |
|---|---|---|---|
tenantId * | path | string | |
accountId * | path | string |
201Added.401The platform API key is missing or wrong, or there is no valid admin panel session.403A team admin asked for something only the platform operator may do, or for another team, or the CSRF token is missing or wrong.404No such object in this tenant (including objects of other tenants).422A value is out of range, or names a domain or account of another tenant. When one setting is refused, `field` names it and `detail` says what is allowed.
/v1/tenants/{tenantId}/tickets
team admins
A tenant's tickets
Staff see all of them; a team's owners and admins see the team queue and abuse tickets about the team, never what a user sent to platform support.
| Parameter | In | Type | |
|---|---|---|---|
tenantId * | path | string | |
status | query | string | active: open and pending. |
kind | query | string | |
limit | query | integer | |
accountId | query | string |
200The tickets.401The platform API key is missing or wrong, or there is no valid admin panel session.403A team admin asked for something only the platform operator may do, or for another team, or the CSRF token is missing or wrong.404No such object in this tenant (including objects of other tenants).
/v1/tenants/{tenantId}/tickets
team admins
Open a ticket
With accountId, about (and to) that user; without, from the caller. A team opens team tickets. Platform tickets (staff's, abuse tickets, or a team asking with queue platform) go to the Zappocity help desk at once, in its support, billing or abuse queue, and the answer is the ticket here, closed, with zappocityRef; a user staff opened it about is emailed by Zappocity.
| Parameter | In | Type | |
|---|---|---|---|
tenantId * | path | string |
{
"accountId": "3d05579f-ed2b-40c4-a5a7-5a3717b216ec",
"body": "We saw 4000 messages in an hour.",
"kind": "abuse",
"subject": "Spam sent from your account"
}
201The ticket.401The platform API key is missing or wrong, or there is no valid admin panel session.403A team admin asked for something only the platform operator may do, or for another team, or the CSRF token is missing or wrong.404No such object in this tenant (including objects of other tenants).422A value is out of range, or names a domain or account of another tenant. When one setting is refused, `field` names it and `detail` says what is allowed.
/v1/tenants/{tenantId}/tickets/{ticketId}
team admins
A ticket and its messages
Team admins see replies and team notes; staff also see staff notes.
| Parameter | In | Type | |
|---|---|---|---|
tenantId * | path | string | |
ticketId * | path | integer |
200The ticket and messages, oldest first.401The platform API key is missing or wrong, or there is no valid admin panel session.403A team admin asked for something only the platform operator may do, or for another team, or the CSRF token is missing or wrong.404No such object in this tenant (including objects of other tenants).
/v1/tenants/{tenantId}/tickets/{ticketId}
team admins
Change a ticket's status, priority, queue, assignee or kind
A team cannot resolve or close abuse tickets, change the kind, or take a ticket back from platform support. Setting queue to platform sends the ticket, with its whole history (internal notes as internal), to the Zappocity help desk; it is closed here with zappocityRef and a note saying where it went.
| Parameter | In | Type | |
|---|---|---|---|
tenantId * | path | string | |
ticketId * | path | integer |
200The ticket.401The platform API key is missing or wrong, or there is no valid admin panel session.403A team admin asked for something only the platform operator may do, or for another team, or the CSRF token is missing or wrong.404No such object in this tenant (including objects of other tenants).422A value is out of range, or names a domain or account of another tenant. When one setting is refused, `field` names it and `detail` says what is allowed.
/v1/tenants/{tenantId}/tickets/{ticketId}/messages
team admins
Reply, or add an internal note
A reply is emailed to the requester and marks the ticket pending (waiting for them). internal true (or "team") is a note for the team's admins and staff; "staff" is for staff only. Notes change nothing else.
| Parameter | In | Type | |
|---|---|---|---|
tenantId * | path | string | |
ticketId * | path | integer |
204Added.401The platform API key is missing or wrong, or there is no valid admin panel session.403A team admin asked for something only the platform operator may do, or for another team, or the CSRF token is missing or wrong.404No such object in this tenant (including objects of other tenants).422A value is out of range, or names a domain or account of another tenant. When one setting is refused, `field` names it and `detail` says what is allowed.
/v1/tickets
The platform support queue
Tickets sent to platform support (queue=team or all=1 for others), open first, then by priority (urgent, high: teams with priority_support start high) and last change.
| Parameter | In | Type | |
|---|---|---|---|
status | query | string | active: open and pending. |
kind | query | string | |
limit | query | integer | |
queue | query | string | |
all | query | string | Any value lists every queue. |
200The tickets.401The platform API key is missing or wrong, or there is no valid admin panel session.403A team admin asked for something only the platform operator may do, or for another team, or the CSRF token is missing or wrong.404No such object in this tenant (including objects of other tenants).
Tenants
/v1/tenants
List tenants
| Parameter | In | Type | |
|---|---|---|---|
externalRef | query | string | Return only the tenant with this external reference. |
200Tenants, oldest first.401The platform API key is missing or wrong, or there is no valid admin panel session.
/v1/tenants
Create a tenant
201Created.400The body is not a valid JSON object, has unknown fields, or is too large.401The platform API key is missing or wrong, or there is no valid admin panel session.409The name or address is taken, the tenant has no free seats, a plan limit would be passed (the body adds limit, used and max), or the object is still in use.422A value is out of range, or names a domain or account of another tenant. When one setting is refused, `field` names it and `detail` says what is allowed.
/v1/tenants/{tenantId}
team admins
Get a tenant
| Parameter | In | Type | |
|---|---|---|---|
tenantId * | path | string |
200The tenant.401The platform API key is missing or wrong, or there is no valid admin panel session.404No such object in this tenant (including objects of other tenants).
/v1/tenants/{tenantId}
Rename, resize, suspend or reactivate a tenant
Suspending a tenant blocks sign-in and mail delivery for all of its accounts at once; nothing is deleted.
| Parameter | In | Type | |
|---|---|---|---|
tenantId * | path | string |
200The updated tenant.400The body is not a valid JSON object, has unknown fields, or is too large.401The platform API key is missing or wrong, or there is no valid admin panel session.404No such object in this tenant (including objects of other tenants).422A value is out of range, or names a domain or account of another tenant. When one setting is refused, `field` names it and `detail` says what is allowed.
/v1/tenants/{tenantId}/api-keys
team admins
The team's API keys (never the keys themselves)
| Parameter | In | Type | |
|---|---|---|---|
tenantId * | path | string |
200The keys.401The platform API key is missing or wrong, or there is no valid admin panel session.403A team admin asked for something only the platform operator may do, or for another team, or the CSRF token is missing or wrong.404No such object in this tenant (including objects of other tenants).
/v1/tenants/{tenantId}/api-keys
team admins
Make an API key for the team
Needs api_access in the plan. The answer carries key, which is never shown again; only its SHA-256 is stored. A team can have 20 keys. API keys cannot call this.
| Parameter | In | Type | |
|---|---|---|---|
tenantId * | path | string |
{
"name": "HR sync"
}
201The key, shown this once.401The platform API key is missing or wrong, or there is no valid admin panel session.403A team admin asked for something only the platform operator may do, or for another team, or the CSRF token is missing or wrong.404No such object in this tenant (including objects of other tenants).409The name or address is taken, the tenant has no free seats, a plan limit would be passed (the body adds limit, used and max), or the object is still in use.422A value is out of range, or names a domain or account of another tenant. When one setting is refused, `field` names it and `detail` says what is allowed.
/v1/tenants/{tenantId}/api-keys/{keyId}
team admins
Revoke an API key
| Parameter | In | Type | |
|---|---|---|---|
tenantId * | path | string | |
keyId * | path | string |
204Revoked; requests with it answer 401 at once.401The platform API key is missing or wrong, or there is no valid admin panel session.403A team admin asked for something only the platform operator may do, or for another team, or the CSRF token is missing or wrong.404No such object in this tenant (including objects of other tenants).
/v1/tenants/{tenantId}/approve
Approve a signup that waits for approval
A free tenant becomes active at once; a paid one still waits for payment.
| Parameter | In | Type | |
|---|---|---|---|
tenantId * | path | string |
200The tenant.401The platform API key is missing or wrong, or there is no valid admin panel session.404No such object in this tenant (including objects of other tenants).
/v1/tenants/{tenantId}/reject
Reject a signup
The tenant is suspended and marked rejected; it cannot sign in or receive mail.
| Parameter | In | Type | |
|---|---|---|---|
tenantId * | path | string |
200The tenant.401The platform API key is missing or wrong, or there is no valid admin panel session.404No such object in this tenant (including objects of other tenants).
/v1/tenants/{tenantId}/spam-policy
team admins
The team's spam thresholds and sender lists
| Parameter | In | Type | |
|---|---|---|---|
tenantId * | path | string |
200The policy; empty when none is set.401The platform API key is missing or wrong, or there is no valid admin panel session.403A team admin asked for something only the platform operator may do, or for another team, or the CSRF token is missing or wrong.404No such object in this tenant (including objects of other tenants).
/v1/tenants/{tenantId}/spam-policy
team admins
Replace the team's spam thresholds and sender lists
Null scores use the platform's. allow and block hold addresses or domains (a domain covers its subdomains), up to 1000 each, and apply to everyone in the team on top of each user's own. Allowed senders are never filed in Junk or refused as spam (viruses still are); blocked senders always go to Junk.
| Parameter | In | Type | |
|---|---|---|---|
tenantId * | path | string |
{
"allow": [
"partner.example"
],
"block": [
"spammy.example",
"someone@bad.example"
],
"junkScore": 5,
"rejectScore": 12
}
200The new policy.400The body is not a valid JSON object, has unknown fields, or is too large.401The platform API key is missing or wrong, or there is no valid admin panel session.403A team admin asked for something only the platform operator may do, or for another team, or the CSRF token is missing or wrong.404No such object in this tenant (including objects of other tenants).422A value is out of range, or names a domain or account of another tenant. When one setting is refused, `field` names it and `detail` says what is allowed.
/v1/tenants/{tenantId}/spam-reports
team admins
The team's spam and phishing reports, newest first
Users report messages from webmail (POST /api/mail/report); a copy of each message is kept with its report.
| Parameter | In | Type | |
|---|---|---|---|
tenantId * | path | string | |
status | query | string | |
limit | query | integer |
200The reports.400The body is not a valid JSON object, has unknown fields, or is too large.401The platform API key is missing or wrong, or there is no valid admin panel session.403A team admin asked for something only the platform operator may do, or for another team, or the CSRF token is missing or wrong.404No such object in this tenant (including objects of other tenants).
/v1/tenants/{tenantId}/spam-reports/{reportId}
team admins
One report
| Parameter | In | Type | |
|---|---|---|---|
tenantId * | path | string | |
reportId * | path | string |
200The report.401The platform API key is missing or wrong, or there is no valid admin panel session.403A team admin asked for something only the platform operator may do, or for another team, or the CSRF token is missing or wrong.404No such object in this tenant (including objects of other tenants).
/v1/tenants/{tenantId}/spam-reports/{reportId}
team admins
Resolve or reopen a report, or change its note
Resolving records who did it; reopening clears that. The note is for administrators only.
| Parameter | In | Type | |
|---|---|---|---|
tenantId * | path | string | |
reportId * | path | string |
{
"note": "Blocked the sender's domain for the team.",
"status": "resolved"
}
200The report.401The platform API key is missing or wrong, or there is no valid admin panel session.403A team admin asked for something only the platform operator may do, or for another team, or the CSRF token is missing or wrong.404No such object in this tenant (including objects of other tenants).422A value is out of range, or names a domain or account of another tenant. When one setting is refused, `field` names it and `detail` says what is allowed.
/v1/tenants/{tenantId}/spam-reports/{reportId}/message
team admins
The reported message, as an .eml file to save
Served as an attachment with a sandboxing policy; it may be a phishing message, so it is never shown in the panel.
| Parameter | In | Type | |
|---|---|---|---|
tenantId * | path | string | |
reportId * | path | string |
200The message as received (up to 10 MiB).401The platform API key is missing or wrong, or there is no valid admin panel session.403A team admin asked for something only the platform operator may do, or for another team, or the CSRF token is missing or wrong.404No such object in this tenant (including objects of other tenants).
/v1/tenants/{tenantId}/spam-reports/{reportId}/quarantine
team admins
Move every copy of the reported message in the team to Junk
Copies are found by Message-ID in every mailbox of the tenant and moved to each owner's Junk (which also teaches the spam filter). A message without a Message-ID cannot be found; moved is then 0.
| Parameter | In | Type | |
|---|---|---|---|
tenantId * | path | string | |
reportId * | path | string |
200How many copies were moved, and the report.{ "moved": 4, "report": { "id": "6f1e...", "kind": "phishing", "quarantined": 4 } }401The platform API key is missing or wrong, or there is no valid admin panel session.403A team admin asked for something only the platform operator may do, or for another team, or the CSRF token is missing or wrong.404No such object in this tenant (including objects of other tenants).
/v1/tenants/{tenantId}/team-settings
team admins
The team's joining, welcome and page settings
| Parameter | In | Type | |
|---|---|---|---|
tenantId * | path | string |
200The settings, with the join page's address.401The platform API key is missing or wrong, or there is no valid admin panel session.403A team admin asked for something only the platform operator may do, or for another team, or the CSRF token is missing or wrong.404No such object in this tenant (including objects of other tenants).
/v1/tenants/{tenantId}/team-settings
team admins
Change the team's joining, welcome and page settings
signupOpen: true needs team_signup in the plan (403 otherwise). signupDomain must be one of the team's own domains; an empty string clears it, and then nobody can join. With welcomeEnabled, new members (from the join page, or added by an administrator or the API) get the team's message instead of the platform's; {name}, {address} and {webmail} are filled in. accent is #rrggbb or empty.
| Parameter | In | Type | |
|---|---|---|---|
tenantId * | path | string |
{
"accent": "#1a73e8",
"signupDomain": "acme.com",
"welcomeBody": "Hi {name}, your address is {address}.",
"welcomeEnabled": true
}
200The new settings.400The body is not a valid JSON object, has unknown fields, or is too large.401The platform API key is missing or wrong, or there is no valid admin panel session.403A team admin asked for something only the platform operator may do, or for another team, or the CSRF token is missing or wrong.404No such object in this tenant (including objects of other tenants).422A value is out of range, or names a domain or account of another tenant. When one setting is refused, `field` names it and `detail` says what is allowed.
/v1/tenants/{tenantId}/usage
team admins
What the tenant uses against its plan
| Parameter | In | Type | |
|---|---|---|---|
tenantId * | path | string |
200Counts and the plan's limits.401The platform API key is missing or wrong, or there is no valid admin panel session.404No such object in this tenant (including objects of other tenants).
Vacation replies
/v1/tenants/{tenantId}/accounts/{accountId}/vacation
team admins
Get an account's vacation response
An account that never set one has a disabled, empty one.
| Parameter | In | Type | |
|---|---|---|---|
tenantId * | path | string | |
accountId * | path | string |
200The vacation response.401The platform API key is missing or wrong, or there is no valid admin panel session.404No such object in this tenant (including objects of other tenants).
/v1/tenants/{tenantId}/accounts/{accountId}/vacation
team admins
Set an account's vacation response
Replaces the whole response. While isEnabled is true and the time is between fromDate and toDate (either may be null for open ended), mail addressed to the account gets an automatic reply, at most once per sender every 7 days, unless the account's filters sent one with the Sieve vacation action. Mailing lists, bulk and automatic mail, bounces and mail in Junk never get a reply.
| Parameter | In | Type | |
|---|---|---|---|
tenantId * | path | string | |
accountId * | path | string |
{
"fromDate": "2026-12-20T00:00:00Z",
"htmlBody": null,
"isEnabled": true,
"subject": "Out of office",
"textBody": "I am away until 4 January and will reply when I am back.",
"toDate": "2027-01-04T00:00:00Z"
}
200The vacation response as stored.400The body is not a valid JSON object, has unknown fields, or is too large.401The platform API key is missing or wrong, or there is no valid admin panel session.404No such object in this tenant (including objects of other tenants).422A value is out of range, or names a domain or account of another tenant. When one setting is refused, `field` names it and `detail` says what is allowed.