Mailocity admin API

version 1.35.0 OpenAPI spec (YAML)

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

GET /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.

ParameterInType
qquerystring
limitqueryinteger
Responses
  • 200 The 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"
        }
      ]
    }
  • 401 The platform API key is missing or wrong, or there is no valid admin panel session.
GET /v1/tenants/{tenantId}/accounts team admins

List the tenant's accounts

ParameterInType
tenantId *pathstring
Responses
  • 200 Accounts, oldest first.
  • 401 The platform API key is missing or wrong, or there is no valid admin panel session.
  • 404 No such object in this tenant (including objects of other tenants).
POST /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.

ParameterInType
tenantId *pathstring
Responses
  • 201 Created.
  • 400 The body is not a valid JSON object, has unknown fields, or is too large.
  • 401 The platform API key is missing or wrong, or there is no valid admin panel session.
  • 404 No such object in this tenant (including objects of other tenants).
  • 409 The 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.
  • 422 A 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.
GET /v1/tenants/{tenantId}/accounts/{accountId} team admins

Get an account

ParameterInType
tenantId *pathstring
accountId *pathstring
Responses
  • 200 The account.
  • 401 The platform API key is missing or wrong, or there is no valid admin panel session.
  • 404 No such object in this tenant (including objects of other tenants).
PATCH /v1/tenants/{tenantId}/accounts/{accountId} team admins

Change an account's name, role, status, password or recovery address

ParameterInType
tenantId *pathstring
accountId *pathstring
Responses
  • 200 The updated account.
  • 400 The body is not a valid JSON object, has unknown fields, or is too large.
  • 401 The platform API key is missing or wrong, or there is no valid admin panel session.
  • 404 No such object in this tenant (including objects of other tenants).
  • 422 A 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.
GET /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.

ParameterInType
tenantId *pathstring
accountId *pathstring
Responses
  • 200 The 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"
          ]
        }
      ]
    }
  • 401 The platform API key is missing or wrong, or there is no valid admin panel session.
  • 404 No such object in this tenant (including objects of other tenants).
POST /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.

ParameterInType
tenantId *pathstring
accountId *pathstring
Request
{
  "name": "Zappocity mail",
  "scopes": [
    "send"
  ]
}
Responses
  • 201 The token, shown once.
    {
      "createdBy": "the platform API key",
      "id": "5d1e2f3a-4b5c-4d6e-8f70-819203a4b5c6",
      "name": "Zappocity mail",
      "scopes": [
        "send"
      ],
      "token": "cs_mt_Yk3"
    }
  • 401 The platform API key is missing or wrong, or there is no valid admin panel session.
  • 404 No such object in this tenant (including objects of other tenants).
  • 409 The 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.
  • 422 A 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.
DELETE /v1/tenants/{tenantId}/accounts/{accountId}/api-tokens/{tokenId}

Revoke a mailbox API token

ParameterInType
tenantId *pathstring
accountId *pathstring
tokenId *pathstring
Responses
  • 204 Revoked.
  • 401 The platform API key is missing or wrong, or there is no valid admin panel session.
  • 404 No such object in this tenant (including objects of other tenants).
GET /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.

ParameterInType
tenantId *pathstring
accountId *pathstring
Responses
  • 200 The 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"
        }
      ]
    }
  • 401 The platform API key is missing or wrong, or there is no valid admin panel session.
  • 404 No such object in this tenant (including objects of other tenants).
DELETE /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).

ParameterInType
tenantId *pathstring
accountId *pathstring
appPasswordId *pathstring
Responses
  • 204 The app password is revoked.
  • 401 The platform API key is missing or wrong, or there is no valid admin panel session.
  • 404 No such object in this tenant (including objects of other tenants).
GET /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.

ParameterInType
tenantId *pathstring
accountId *pathstring
Responses
  • 200 The 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"
        }
      ]
    }
  • 401 The platform API key is missing or wrong, or there is no valid admin panel session.
  • 403 A team admin asked for something only the platform operator may do, or for another team, or the CSRF token is missing or wrong.
  • 404 No such object in this tenant (including objects of other tenants).
POST /v1/tenants/{tenantId}/accounts/{accountId}/domain-blocks team admins

Block a domain

ParameterInType
tenantId *pathstring
accountId *pathstring
Request
{
  "action": "reject",
  "kind": "named",
  "note": "Links in the invoice scam",
  "value": "phish.example"
}
Responses
  • 201 The block.
  • 401 The platform API key is missing or wrong, or there is no valid admin panel session.
  • 403 A team admin asked for something only the platform operator may do, or for another team, or the CSRF token is missing or wrong.
  • 404 No such object in this tenant (including objects of other tenants).
  • 409 The 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.
  • 422 A 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.
DELETE /v1/tenants/{tenantId}/accounts/{accountId}/domain-blocks/{blockId} team admins

Remove a domain block

ParameterInType
tenantId *pathstring
accountId *pathstring
blockId *pathinteger
Responses
  • 204 Removed.
  • 401 The platform API key is missing or wrong, or there is no valid admin panel session.
  • 403 A team admin asked for something only the platform operator may do, or for another team, or the CSRF token is missing or wrong.
  • 404 No such object in this tenant (including objects of other tenants).
GET /v1/tenants/{tenantId}/accounts/{accountId}/entitlements team admins

A user's features, from the plan and their own switches

ParameterInType
tenantId *pathstring
accountId *pathstring
Responses
  • 200 The plan's entitlements, the user's switches and the result.
  • 401 The platform API key is missing or wrong, or there is no valid admin panel session.
  • 403 A team admin asked for something only the platform operator may do, or for another team, or the CSRF token is missing or wrong.
  • 404 No such object in this tenant (including objects of other tenants).
PUT /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.

ParameterInType
tenantId *pathstring
accountId *pathstring
Request
{
  "off": [
    "imap",
    "forwarding"
  ]
}
Responses
  • 200 The new state.
  • 400 The body is not a valid JSON object, has unknown fields, or is too large.
  • 401 The platform API key is missing or wrong, or there is no valid admin panel session.
  • 403 A team admin asked for something only the platform operator may do, or for another team, or the CSRF token is missing or wrong.
  • 404 No such object in this tenant (including objects of other tenants).
  • 422 A 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.
POST /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.

ParameterInType
tenantId *pathstring
accountId *pathstring
Request
{
  "reason": "Ticket 4182: filters not working"
}
Responses
  • 201 The link.
    {
      "expiresAt": "2026-10-08T12:05:00Z",
      "url": "https://webmail.example.com/#impersonate=Qm1..."
    }
  • 401 The platform API key is missing or wrong, or there is no valid admin panel session.
  • 403 A team admin asked for something only the platform operator may do, or for another team, or the CSRF token is missing or wrong.
  • 404 No such object in this tenant (including objects of other tenants).
  • 422 A 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.
GET /v1/tenants/{tenantId}/accounts/{accountId}/imports team admins

A user's imports from other mail servers, newest first

ParameterInType
tenantId *pathstring
accountId *pathstring
Responses
  • 200 The imports.
  • 401 The platform API key is missing or wrong, or there is no valid admin panel session.
  • 403 A team admin asked for something only the platform operator may do, or for another team, or the CSRF token is missing or wrong.
  • 404 No such object in this tenant (including objects of other tenants).
POST /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.

ParameterInType
tenantId *pathstring
accountId *pathstring
Request
{
  "host": "imap.gmail.com",
  "password": "app password",
  "skipFolders": [
    "[Gmail]/Spam"
  ],
  "username": "amy@gmail.com"
}
Responses
  • 201 Queued.
  • 401 The platform API key is missing or wrong, or there is no valid admin panel session.
  • 403 A team admin asked for something only the platform operator may do, or for another team, or the CSRF token is missing or wrong.
  • 404 No such object in this tenant (including objects of other tenants).
  • 409 The 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.
  • 422 A 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.
DELETE /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.

ParameterInType
tenantId *pathstring
accountId *pathstring
importId *pathstring
Responses
  • 204 Cancelled.
  • 401 The platform API key is missing or wrong, or there is no valid admin panel session.
  • 403 A team admin asked for something only the platform operator may do, or for another team, or the CSRF token is missing or wrong.
  • 404 No such object in this tenant (including objects of other tenants).
  • 409 The 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.
POST /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.

ParameterInType
tenantId *pathstring
accountId *pathstring
Request
{
  "caseReference": "Subpoena 2026-117",
  "from": "2026-01-01",
  "include": [
    "account",
    "logins",
    "messages",
    "content"
  ],
  "to": "2026-10-01"
}
Responses
  • 200 The ZIP file.
  • 401 The platform API key is missing or wrong, or there is no valid admin panel session.
  • 404 No such object in this tenant (including objects of other tenants).
  • 422 A 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.
GET /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.

ParameterInType
tenantId *pathstring
accountId *pathstring
Responses
  • 200 The 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
      }
    }
  • 401 The platform API key is missing or wrong, or there is no valid admin panel session.
  • 404 No such object in this tenant (including objects of other tenants).
PUT /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.

ParameterInType
tenantId *pathstring
accountId *pathstring
Responses
  • 200 The user's limits after the change.
  • 401 The platform API key is missing or wrong, or there is no valid admin panel session.
  • 404 No such object in this tenant (including objects of other tenants).
  • 422 A 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.
DELETE /v1/tenants/{tenantId}/accounts/{accountId}/lockout team admins

Lift a lockout

ParameterInType
tenantId *pathstring
accountId *pathstring
Responses
  • 204 Unlocked; the failure count starts again.
  • 401 The platform API key is missing or wrong, or there is no valid admin panel session.
  • 403 A team admin asked for something only the platform operator may do, or for another team, or the CSRF token is missing or wrong.
  • 404 No such object in this tenant (including objects of other tenants).
GET /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.

ParameterInType
tenantId *pathstring
accountId *pathstring
daysqueryintegerAt most what the settings keep.
Responses
  • 200 The attempts.
  • 401 The platform API key is missing or wrong, or there is no valid admin panel session.
  • 403 A team admin asked for something only the platform operator may do, or for another team, or the CSRF token is missing or wrong.
  • 404 No such object in this tenant (including objects of other tenants).
DELETE /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.

ParameterInType
tenantId *pathstring
accountId *pathstring
Responses
  • 204 Resumed.
  • 401 The platform API key is missing or wrong, or there is no valid admin panel session.
  • 403 A team admin asked for something only the platform operator may do, or for another team, or the CSRF token is missing or wrong.
  • 404 No such object in this tenant (including objects of other tenants).
GET /v1/tenants/{tenantId}/accounts/{accountId}/sessions team admins

A user's open webmail sessions

ParameterInType
tenantId *pathstring
accountId *pathstring
Responses
  • 200 The sessions, last used first.
  • 401 The platform API key is missing or wrong, or there is no valid admin panel session.
  • 403 A team admin asked for something only the platform operator may do, or for another team, or the CSRF token is missing or wrong.
  • 404 No such object in this tenant (including objects of other tenants).
DELETE /v1/tenants/{tenantId}/accounts/{accountId}/sessions team admins

Sign a user out of webmail everywhere

ParameterInType
tenantId *pathstring
accountId *pathstring
Responses
  • 204 Signed out.
  • 401 The platform API key is missing or wrong, or there is no valid admin panel session.
  • 403 A team admin asked for something only the platform operator may do, or for another team, or the CSRF token is missing or wrong.
  • 404 No such object in this tenant (including objects of other tenants).
GET /v1/tenants/{tenantId}/accounts/{accountId}/shares team admins

Who a mailbox is shared with

Teammates who see this mailbox next to their own in webmail and JMAP (RFC 9670), and how: read (read only), write (read and change) or send (also send as it). Users manage their own in webmail Settings; see docs/kb/sharing.md.

ParameterInType
tenantId *pathstring
accountId *pathstring
Responses
  • 200 The shares.
    {
      "items": [
        {
          "access": "send",
          "createdAt": "2026-10-08T15:00:00Z",
          "grantee": "assistant@acme.test",
          "granteeId": "6f1c2a9e-1b7d-4f0e-9a3c-2d8b5e4f7a10",
          "granteeName": "Sam",
          "owner": "boss@acme.test"
        }
      ]
    }
  • 401 The platform API key is missing or wrong, or there is no valid admin panel session.
  • 403 A team admin asked for something only the platform operator may do, or for another team, or the CSRF token is missing or wrong.
  • 404 No such object in this tenant (including objects of other tenants).
PUT /v1/tenants/{tenantId}/accounts/{accountId}/shares/{grantee} team admins

Share a mailbox with a teammate, or change how

Only within a team. The teammate gets a ShareNotification. send implies write.

ParameterInType
tenantId *pathstring
accountId *pathstring
grantee *pathstringThe teammate's account id or address.
Request
{
  "access": "write"
}
Responses
  • 200 The share.
  • 401 The platform API key is missing or wrong, or there is no valid admin panel session.
  • 403 A team admin asked for something only the platform operator may do, or for another team, or the CSRF token is missing or wrong.
  • 404 No such object in this tenant (including objects of other tenants).
  • 422 A 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.
DELETE /v1/tenants/{tenantId}/accounts/{accountId}/shares/{grantee} team admins

Stop sharing a mailbox with a teammate

ParameterInType
tenantId *pathstring
accountId *pathstring
grantee *pathstringThe teammate's account id or address.
Responses
  • 204 No longer shared.
  • 401 The platform API key is missing or wrong, or there is no valid admin panel session.
  • 403 A team admin asked for something only the platform operator may do, or for another team, or the CSRF token is missing or wrong.
  • 404 No such object in this tenant (including objects of other tenants).
GET /v1/tenants/{tenantId}/accounts/{accountId}/signup-checks

What the checks found when the account signed up

ParameterInType
tenantId *pathstring
accountId *pathstring
Responses
  • 200 The report, or {"checked":false}.
  • 401 The platform API key is missing or wrong, or there is no valid admin panel session.
  • 404 No such object in this tenant (including objects of other tenants).
GET /v1/tenants/{tenantId}/accounts/{accountId}/spam-policy team admins

A user's own spam threshold and sender lists

ParameterInType
tenantId *pathstring
accountId *pathstring
Responses
  • 200 The policy; empty when none is set.
  • 401 The platform API key is missing or wrong, or there is no valid admin panel session.
  • 403 A team admin asked for something only the platform operator may do, or for another team, or the CSRF token is missing or wrong.
  • 404 No such object in this tenant (including objects of other tenants).
PUT /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.

ParameterInType
tenantId *pathstring
accountId *pathstring
Request
{
  "allow": [
    "newsletter.example"
  ],
  "junkScore": 4
}
Responses
  • 200 The new policy.
  • 400 The body is not a valid JSON object, has unknown fields, or is too large.
  • 401 The platform API key is missing or wrong, or there is no valid admin panel session.
  • 403 A team admin asked for something only the platform operator may do, or for another team, or the CSRF token is missing or wrong.
  • 404 No such object in this tenant (including objects of other tenants).
  • 422 A 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.
GET /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).

ParameterInType
tenantId *pathstring
accountId *pathstring
Responses
  • 200 The recipients.
    {
      "items": [
        {
          "createdAt": "2026-10-09T03:00:00Z",
          "reason": "complaint",
          "recipient": "carol@example.net"
        }
      ]
    }
  • 401 The platform API key is missing or wrong, or there is no valid admin panel session.
  • 403 A team admin asked for something only the platform operator may do, or for another team, or the CSRF token is missing or wrong.
  • 404 No such object in this tenant (including objects of other tenants).
DELETE /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.

ParameterInType
tenantId *pathstring
accountId *pathstring
recipient *pathstring
Responses
  • 204 Removed.
  • 401 The platform API key is missing or wrong, or there is no valid admin panel session.
  • 403 A team admin asked for something only the platform operator may do, or for another team, or the CSRF token is missing or wrong.
  • 404 No such object in this tenant (including objects of other tenants).
DELETE /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.

ParameterInType
tenantId *pathstring
accountId *pathstring
Responses
  • 204 Two-step sign-in is off.
  • 401 The platform API key is missing or wrong, or there is no valid admin panel session.
  • 404 No such object in this tenant (including objects of other tenants).
PUT /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.

ParameterInType
tenantId *pathstring
accountId *pathstring
Request
{
  "note": "Called the customer on their number on file.",
  "verified": true
}
Responses
  • 200 The account.
  • 401 The platform API key is missing or wrong, or there is no valid admin panel session.
  • 404 No such object in this tenant (including objects of other tenants).
  • 422 A 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

GET /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.

Responses
  • 200 The session.
  • 401 The platform API key is missing or wrong, or there is no valid admin panel session.
POST /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.

Request
{
  "email": "ops@example.com",
  "password": "a long passphrase"
}
Responses
  • 201 Signed in. The response sets the session cookie.
  • 400 The body is not a valid JSON object, has unknown fields, or is too large.
  • 401 The platform API key is missing or wrong, or there is no valid admin panel session.
  • 415 The body is not JSON.
  • 429 Too many failed sign-ins.
DELETE /v1/session

Sign out

Ends the session on the server and clears the cookie. Needs X-CSRF-Token.

Responses
  • 204 Signed out.
  • 401 The platform API key is missing or wrong, or there is no valid admin panel session.
  • 403 The CSRF token is missing or wrong.
GET /v1/session/options

Which ways of signing in to the panel are open

Responses
  • 200 Whether Sign in with Zappocity is offered, and whether passwords still work.
    {
      "passwordSignin": true,
      "sso": true
    }
GET /v1/session/sso/callback

Where Zappocity sends the browser back

ParameterInType
codequerystring
statequerystring
Responses
  • 302 To /admin, signed in; or to /admin#sso-error=... (expired, unverified, not-staff, unavailable).
GET /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.

Responses
  • 302 To Zappocity

Aliases

GET /v1/tenants/{tenantId}/aliases team admins

List the tenant's aliases

ParameterInType
tenantId *pathstring
Responses
  • 200 Aliases by address.
  • 401 The platform API key is missing or wrong, or there is no valid admin panel session.
  • 404 No such object in this tenant (including objects of other tenants).
POST /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).

ParameterInType
tenantId *pathstring
Responses
  • 201 Created.
  • 400 The body is not a valid JSON object, has unknown fields, or is too large.
  • 401 The platform API key is missing or wrong, or there is no valid admin panel session.
  • 404 No such object in this tenant (including objects of other tenants).
  • 409 The 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.
  • 422 A 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.
DELETE /v1/tenants/{tenantId}/aliases/{address} team admins

Remove an alias

ParameterInType
tenantId *pathstring
address *pathstring
Responses
  • 204 Removed.
  • 401 The platform API key is missing or wrong, or there is no valid admin panel session.
  • 404 No such object in this tenant (including objects of other tenants).

Compliance archive

PUT /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.

ParameterInType
tenantId *pathstring
accountId *pathstring
Request
{
  "reason": "Smith v. Example, preserve from 2026-03"
}
Responses
  • 200 The hold.
  • 401 The platform API key is missing or wrong, or there is no valid admin panel session.
  • 403 A team admin asked for something only the platform operator may do, or for another team, or the CSRF token is missing or wrong.
  • 404 No such object in this tenant (including objects of other tenants).
  • 422 A 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.
DELETE /v1/tenants/{tenantId}/accounts/{accountId}/hold team admins

Lift a user's legal hold

ParameterInType
tenantId *pathstring
accountId *pathstring
Responses
  • 204 Lifted (or there was none).
  • 401 The platform API key is missing or wrong, or there is no valid admin panel session.
  • 403 A team admin asked for something only the platform operator may do, or for another team, or the CSRF token is missing or wrong.
  • 404 No such object in this tenant (including objects of other tenants).
GET /v1/tenants/{tenantId}/archive team admins

Search the archive

Newest first. Each search is written to the access log with its terms.

ParameterInType
tenantId *pathstring
qquerystringWords in the subject, addresses or body.
addressquerystringThe user, sender or a recipient.
directionquerystring
fromquerystringArchived on or after: a date (2026-01-31) or an RFC 3339 time.
toquerystringArchived before.
beforequeryintegerPage: ids below this one.
limitqueryinteger
Responses
  • 200 Matching 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"
        }
      ]
    }
  • 400 The body is not a valid JSON object, has unknown fields, or is too large.
  • 401 The platform API key is missing or wrong, or there is no valid admin panel session.
  • 403 A team admin asked for something only the platform operator may do, or for another team, or the CSRF token is missing or wrong.
  • 404 No such object in this tenant (including objects of other tenants).
GET /v1/tenants/{tenantId}/archive-access team admins

Who searched, downloaded or changed the archive

ParameterInType
tenantId *pathstring
beforequeryinteger
limitqueryinteger
Responses
  • 200 Newest 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
        }
      ]
    }
  • 400 The body is not a valid JSON object, has unknown fields, or is too large.
  • 401 The platform API key is missing or wrong, or there is no valid admin panel session.
  • 403 A team admin asked for something only the platform operator may do, or for another team, or the CSRF token is missing or wrong.
  • 404 No such object in this tenant (including objects of other tenants).
GET /v1/tenants/{tenantId}/archive-settings team admins

Archiving settings, size and holds

ParameterInType
tenantId *pathstring
Responses
  • 200 The 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"
      }
    }
  • 401 The platform API key is missing or wrong, or there is no valid admin panel session.
  • 403 A team admin asked for something only the platform operator may do, or for another team, or the CSRF token is missing or wrong.
  • 404 No such object in this tenant (including objects of other tenants).
PATCH /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.

ParameterInType
tenantId *pathstring
Request
{
  "enabled": true,
  "retentionDays": 3650
}
Responses
  • 200 The new settings.
  • 401 The platform API key is missing or wrong, or there is no valid admin panel session.
  • 403 A team admin asked for something only the platform operator may do, or for another team, or the CSRF token is missing or wrong.
  • 404 No such object in this tenant (including objects of other tenants).
  • 422 A 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.
GET /v1/tenants/{tenantId}/archive/{messageId}/message team admins

Download an archived message

The message exactly as archived, as an .eml file. Logged.

ParameterInType
tenantId *pathstring
messageId *pathinteger
Responses
  • 200 The message.
  • 401 The platform API key is missing or wrong, or there is no valid admin panel session.
  • 403 A team admin asked for something only the platform operator may do, or for another team, or the CSRF token is missing or wrong.
  • 404 No such object in this tenant (including objects of other tenants).

DMARC reports

GET /v1/shared-domains/{domain}/dmarc-reports

DMARC aggregate reports received for a shared domain

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.

ParameterInType
domain *pathstring
limitqueryinteger
beforequeryintegerOnly reports with a smaller id.
Responses
  • 200 Reports.
  • 400 The body is not a valid JSON object, has unknown fields, or is too large.
  • 401 The platform API key is missing or wrong, or there is no valid admin panel session.
  • 404 No such object in this tenant (including objects of other tenants).
GET /v1/shared-domains/{domain}/dmarc-reports/{reportId}

One DMARC report with its rows

ParameterInType
domain *pathstring
reportId *pathinteger
Responses
  • 200 The report; records are sorted by message count, largest first.
  • 401 The platform API key is missing or wrong, or there is no valid admin panel session.
  • 404 No such object in this tenant (including objects of other tenants).
GET /v1/shared-domains/{domain}/dmarc-summary

Sending sources seen in DMARC reports for a shared domain

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.

ParameterInType
domain *pathstring
daysqueryinteger
Responses
  • 200 The summary.
  • 400 The body is not a valid JSON object, has unknown fields, or is too large.
  • 401 The platform API key is missing or wrong, or there is no valid admin panel session.
  • 404 No such object in this tenant (including objects of other tenants).
GET /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.

ParameterInType
tenantId *pathstring
domain *pathstring
limitqueryinteger
beforequeryintegerOnly reports with a smaller id.
Responses
  • 200 Reports.
  • 400 The body is not a valid JSON object, has unknown fields, or is too large.
  • 401 The platform API key is missing or wrong, or there is no valid admin panel session.
  • 404 No such object in this tenant (including objects of other tenants).
GET /v1/tenants/{tenantId}/domains/{domain}/dmarc-reports/{reportId} team admins

One DMARC report with its rows

ParameterInType
tenantId *pathstring
domain *pathstring
reportId *pathinteger
Responses
  • 200 The report; records are sorted by message count, largest first.
  • 401 The platform API key is missing or wrong, or there is no valid admin panel session.
  • 404 No such object in this tenant (including objects of other tenants).
GET /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.

ParameterInType
tenantId *pathstring
domain *pathstring
daysqueryinteger
Responses
  • 200 The summary.
  • 400 The body is not a valid JSON object, has unknown fields, or is too large.
  • 401 The platform API key is missing or wrong, or there is no valid admin panel session.
  • 404 No such object in this tenant (including objects of other tenants).

Domains

POST /v1/shared-domains/{domain}/assign

Make a shared domain a team's own

The domain stops being shared (and leaves the signup page) and becomes the team's, proved, with its settings, DKIM keys, BIMI logo and reports. Every mailbox and alias on it must already be the team's; the team's domain limit applies. A team asking for a shared domain itself is answered 409.

ParameterInType
domain *pathstring
Responses
  • 200 The domain
  • 401 The platform API key is missing or wrong, or there is no valid admin panel session.
  • 403 A team admin asked for something only the platform operator may do, or for another team, or the CSRF token is missing or wrong.
  • 404 No such object in this tenant (including objects of other tenants).
  • 409 The team is at its domain limit.
  • 422 Addresses on the domain belong to other teams.
GET /v1/shared-domains/{domain}/dns-check

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.

ParameterInType
domain *pathstring
Responses
  • 200 The 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."
        }
      ]
    }
  • 401 The platform API key is missing or wrong, or there is no valid admin panel session.
  • 403 A team admin asked for something only the platform operator may do, or for another team, or the CSRF token is missing or wrong.
  • 404 No such object in this tenant (including objects of other tenants).
GET /v1/tenants/{tenantId}/domain-transfers team admins

The team's open domain offers, made and received

ParameterInType
tenantId *pathstring
Responses
  • 200 The offers
  • 401 The platform API key is missing or wrong, or there is no valid admin panel session.
  • 403 A team admin asked for something only the platform operator may do, or for another team, or the CSRF token is missing or wrong.
  • 404 No such object in this tenant (including objects of other tenants).
DELETE /v1/tenants/{tenantId}/domain-transfers/{transferId} team admins

Withdraw (the giving team) or decline (the receiving team) an offer

ParameterInType
tenantId *pathstring
transferId *pathstring
Responses
  • 204 Gone.
  • 401 The platform API key is missing or wrong, or there is no valid admin panel session.
  • 403 A team admin asked for something only the platform operator may do, or for another team, or the CSRF token is missing or wrong.
  • 404 No such object in this tenant (including objects of other tenants).
POST /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.

ParameterInType
tenantId *pathstring
transferId *pathstring
Responses
  • 200 The domain
  • 401 The platform API key is missing or wrong, or there is no valid admin panel session.
  • 403 A team admin asked for something only the platform operator may do, or for another team, or the CSRF token is missing or wrong.
  • 404 No such object in this tenant (including objects of other tenants).
  • 409 A team is not in good standing, the domain is in use, or the team is at its domain limit.
GET /v1/tenants/{tenantId}/domains team admins

List the tenant's domains

ParameterInType
tenantId *pathstring
Responses
  • 200 Domains by name.
  • 401 The platform API key is missing or wrong, or there is no valid admin panel session.
  • 404 No such object in this tenant (including objects of other tenants).
POST /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.

ParameterInType
tenantId *pathstring
Responses
  • 201 Created.
  • 400 The body is not a valid JSON object, has unknown fields, or is too large.
  • 401 The platform API key is missing or wrong, or there is no valid admin panel session.
  • 404 No such object in this tenant (including objects of other tenants).
  • 409 The 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.
  • 422 A 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.
GET /v1/tenants/{tenantId}/domains/{domain} team admins

Get one of the tenant's domains and its settings

ParameterInType
tenantId *pathstring
domain *pathstring
Responses
  • 200 The domain.
  • 401 The platform API key is missing or wrong, or there is no valid admin panel session.
  • 404 No such object in this tenant (including objects of other tenants).
PATCH /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.

ParameterInType
tenantId *pathstring
domain *pathstring
Responses
  • 200 The updated domain.
  • 400 The body is not a valid JSON object, has unknown fields, or is too large.
  • 401 The platform API key is missing or wrong, or there is no valid admin panel session.
  • 404 No such object in this tenant (including objects of other tenants).
  • 422 A 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.
GET /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.

ParameterInType
tenantId *pathstring
domain *pathstring
Responses
  • 200 The 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"
    }
  • 401 The platform API key is missing or wrong, or there is no valid admin panel session.
  • 403 A team admin asked for something only the platform operator may do, or for another team, or the CSRF token is missing or wrong.
  • 404 No such object in this tenant (including objects of other tenants).
PUT /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.

ParameterInType
tenantId *pathstring
domain *pathstring
Request
{
  "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"
}
Responses
  • 200 The logo's state.
  • 400 The body is not a valid JSON object, has unknown fields, or is too large.
  • 401 The platform API key is missing or wrong, or there is no valid admin panel session.
  • 403 A team admin asked for something only the platform operator may do, or for another team, or the CSRF token is missing or wrong.
  • 404 No such object in this tenant (including objects of other tenants).
  • 422 A 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.
DELETE /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.

ParameterInType
tenantId *pathstring
domain *pathstring
Responses
  • 204 Removed.
  • 401 The platform API key is missing or wrong, or there is no valid admin panel session.
  • 403 A team admin asked for something only the platform operator may do, or for another team, or the CSRF token is missing or wrong.
  • 404 No such object in this tenant (including objects of other tenants).
GET /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.

ParameterInType
tenantId *pathstring
domain *pathstring
Responses
  • 200 The 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="
    }
  • 401 The platform API key is missing or wrong, or there is no valid admin panel session.
  • 403 A team admin asked for something only the platform operator may do, or for another team, or the CSRF token is missing or wrong.
  • 404 No such object in this tenant (including objects of other tenants).
  • 409 The 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.
GET /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.

ParameterInType
tenantId *pathstring
domain *pathstring
Responses
  • 200 Keys.
  • 401 The platform API key is missing or wrong, or there is no valid admin panel session.
  • 404 No such object in this tenant (including objects of other tenants).
POST /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.

ParameterInType
tenantId *pathstring
domain *pathstring
Request
{
  "algorithm": "rsa"
}
Responses
  • 201 The key.
  • 400 The body is not a valid JSON object, has unknown fields, or is too large.
  • 401 The platform API key is missing or wrong, or there is no valid admin panel session.
  • 404 No such object in this tenant (including objects of other tenants).
  • 422 A 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.
DELETE /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.

ParameterInType
tenantId *pathstring
domain *pathstring
selector *pathstring
Responses
  • 204 Deleted.
  • 401 The platform API key is missing or wrong, or there is no valid admin panel session.
  • 404 No such object in this tenant (including objects of other tenants).
  • 409 The 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.
POST /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.

ParameterInType
tenantId *pathstring
domain *pathstring
selector *pathstring
Responses
  • 200 The key, now active.
  • 401 The platform API key is missing or wrong, or there is no valid admin panel session.
  • 404 No such object in this tenant (including objects of other tenants).
GET /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.

ParameterInType
tenantId *pathstring
domain *pathstring
formatquerystring`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.
Responses
  • 200 Records 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"
  • 400 The body is not a valid JSON object, has unknown fields, or is too large.
  • 401 The platform API key is missing or wrong, or there is no valid admin panel session.
  • 404 No such object in this tenant (including objects of other tenants).
GET /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.

ParameterInType
tenantId *pathstring
domain *pathstring
Responses
  • 200 The 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."
        }
      ]
    }
  • 401 The platform API key is missing or wrong, or there is no valid admin panel session.
  • 403 A team admin asked for something only the platform operator may do, or for another team, or the CSRF token is missing or wrong.
  • 404 No such object in this tenant (including objects of other tenants).
POST /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.

ParameterInType
tenantId *pathstring
domain *pathstring
Responses
  • 200 The domain
  • 401 The platform API key is missing or wrong, or there is no valid admin panel session.
  • 403 A team admin asked for something only the platform operator may do, or for another team, or the CSRF token is missing or wrong.
  • 404 No such object in this tenant (including objects of other tenants).
  • 409 A team is not in good standing, the domain is in use, or the receiving team is at its domain limit.
GET /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.

ParameterInType
tenantId *pathstring
domain *pathstring
Responses
  • 200 What was found; an empty `problems` means all is well.
    {
      "fetched": true,
      "maxAge": 604800,
      "mode": "enforce",
      "mx": [
        "mx.example.com"
      ],
      "problems": [],
      "recordFound": true,
      "recordId": "20261008T120000"
    }
  • 401 The platform API key is missing or wrong, or there is no valid admin panel session.
  • 403 A team admin asked for something only the platform operator may do, or for another team, or the CSRF token is missing or wrong.
  • 404 No such object in this tenant (including objects of other tenants).
PUT /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.

ParameterInType
tenantId *pathstring
domain *pathstring
Responses
  • 200 The domain.
    {
      "name": "zappo.city",
      "tenantId": "4ee2971f-0fae-4230-8512-ddf1704a769d",
      "verifiedAt": "2026-10-09T02:10:00Z"
    }
  • 401 The platform API key is missing or wrong, or there is no valid admin panel session.
  • 403 A team admin asked for something only the platform operator may do, or for another team, or the CSRF token is missing or wrong.
  • 404 No such object in this tenant (including objects of other tenants).
POST /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.

ParameterInType
tenantId *pathstring
domain *pathstring
Request
{
  "to": "owner@other.example"
}
Responses
  • 201 The 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..."
    }
  • 401 The platform API key is missing or wrong, or there is no valid admin panel session.
  • 403 A team admin asked for something only the platform operator may do, or for another team, or the CSRF token is missing or wrong.
  • 404 No such object in this tenant (including objects of other tenants).
  • 409 A team is not in good standing, or the domain still has mailboxes or aliases.
  • 422 A 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.
POST /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.

ParameterInType
tenantId *pathstring
domain *pathstring
Responses
  • 200 The domain
  • 401 The platform API key is missing or wrong, or there is no valid admin panel session.
  • 403 A team admin asked for something only the platform operator may do, or for another team, or the CSRF token is missing or wrong.
  • 404 No such object in this tenant (including objects of other tenants).
  • 422 The record is not there yet.
GET /v1/tenants/{tenantId}/pending-domains team admins

Domains waiting for their proof record

ParameterInType
tenantId *pathstring
Responses
  • 200 Pending domains by name.
  • 401 The platform API key is missing or wrong, or there is no valid admin panel session.
  • 403 A team admin asked for something only the platform operator may do, or for another team, or the CSRF token is missing or wrong.
  • 404 No such object in this tenant (including objects of other tenants).
POST /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.

ParameterInType
tenantId *pathstring
Responses
  • 201 The record to publish.
  • 400 The body is not a valid JSON object, has unknown fields, or is too large.
  • 401 The platform API key is missing or wrong, or there is no valid admin panel session.
  • 403 A team admin asked for something only the platform operator may do, or for another team, or the CSRF token is missing or wrong.
  • 404 No such object in this tenant (including objects of other tenants).
  • 409 The 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.
  • 422 A 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.
DELETE /v1/tenants/{tenantId}/pending-domains/{domain} team admins

Drop a pending domain request

ParameterInType
tenantId *pathstring
domain *pathstring
Responses
  • 204 Dropped.
  • 401 The platform API key is missing or wrong, or there is no valid admin panel session.
  • 403 A team admin asked for something only the platform operator may do, or for another team, or the CSRF token is missing or wrong.
  • 404 No such object in this tenant (including objects of other tenants).
POST /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.

ParameterInType
tenantId *pathstring
domain *pathstring
Responses
  • 201 The new domain.
  • 401 The platform API key is missing or wrong, or there is no valid admin panel session.
  • 403 A team admin asked for something only the platform operator may do, or for another team, or the CSRF token is missing or wrong.
  • 404 No such object in this tenant (including objects of other tenants).
  • 409 The 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.
  • 422 A 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

GET /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.

ParameterInType
limitqueryinteger
Responses
  • 200 The invitations.
  • 401 The platform API key is missing or wrong, or there is no valid admin panel session.
POST /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.

Request
{
  "count": 5,
  "note": "beta testers",
  "plan": "free",
  "validDays": 7
}
Responses
  • 201 The new invitations, each with its link.
  • 401 The platform API key is missing or wrong, or there is no valid admin panel session.
  • 422 A 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.
DELETE /v1/invitations/{code}

Cancel an unused invitation link

ParameterInType
code *pathstring
Responses
  • 204 Cancelled.
  • 401 The platform API key is missing or wrong, or there is no valid admin panel session.
  • 404 No such object in this tenant (including objects of other tenants).
GET /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).

ParameterInType
tenantId *pathstring
accountId *pathstring
Responses
  • 200 The allowance.
  • 401 The platform API key is missing or wrong, or there is no valid admin panel session.
  • 404 No such object in this tenant (including objects of other tenants).
PATCH /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.

ParameterInType
tenantId *pathstring
accountId *pathstring
Responses
  • 200 The new allowance.
  • 401 The platform API key is missing or wrong, or there is no valid admin panel session.
  • 404 No such object in this tenant (including objects of other tenants).
  • 422 A 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.
GET /v1/tenants/{tenantId}/team-invites team admins

The team's invitation links, newest first

ParameterInType
tenantId *pathstring
Responses
  • 200 The links.
  • 401 The platform API key is missing or wrong, or there is no valid admin panel session.
  • 403 A team admin asked for something only the platform operator may do, or for another team, or the CSRF token is missing or wrong.
  • 404 No such object in this tenant (including objects of other tenants).
POST /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.

ParameterInType
tenantId *pathstring
Request
{
  "days": 30,
  "maxUses": 5,
  "note": "New starters"
}
Responses
  • 201 The link.
  • 401 The platform API key is missing or wrong, or there is no valid admin panel session.
  • 403 A team admin asked for something only the platform operator may do, or for another team, or the CSRF token is missing or wrong.
  • 404 No such object in this tenant (including objects of other tenants).
  • 409 The 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.
  • 422 A 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.
DELETE /v1/tenants/{tenantId}/team-invites/{code} team admins

Stop a link working

ParameterInType
tenantId *pathstring
code *pathstring
Responses
  • 204 Revoked.
  • 401 The platform API key is missing or wrong, or there is no valid admin panel session.
  • 403 A team admin asked for something only the platform operator may do, or for another team, or the CSRF token is missing or wrong.
  • 404 No such object in this tenant (including objects of other tenants).

Mail filters

GET /v1/shared-domains/{domain}/sieve

Get the shared domain rules

ParameterInType
domain *pathstring
Responses
  • 200 The rules; an empty script means none.
  • 401 The platform API key is missing or wrong, or there is no valid admin panel session.
  • 404 No such object in this tenant (including objects of other tenants).
PUT /v1/shared-domains/{domain}/sieve

Replace the shared 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.

ParameterInType
domain *pathstring
Responses
  • 200 The rules as saved.
  • 400 The body is not a valid JSON object, has unknown fields, or is too large.
  • 401 The platform API key is missing or wrong, or there is no valid admin panel session.
  • 404 No such object in this tenant (including objects of other tenants).
  • 422 A 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.
POST /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.

Request
{
  "script": "require \"fileinto\";\nif header :contains \"subject\" \"invoice\" { fileinto \"Invoices\"; }"
}
Responses
  • 200 Whether the script compiles.
  • 400 The body is not a valid JSON object, has unknown fields, or is too large.
  • 401 The platform API key is missing or wrong, or there is no valid admin panel session.
GET /v1/tenants/{tenantId}/accounts/{accountId}/sieve-scripts team admins

List an account's filter scripts

ParameterInType
tenantId *pathstring
accountId *pathstring
Responses
  • 200 The scripts by name, with their content.
  • 401 The platform API key is missing or wrong, or there is no valid admin panel session.
  • 404 No such object in this tenant (including objects of other tenants).
POST /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.

ParameterInType
tenantId *pathstring
accountId *pathstring
Request
{
  "active": true,
  "name": "Newsletters",
  "script": "require \"fileinto\";\nif exists \"list-id\" { fileinto \"Newsletters\"; }"
}
Responses
  • 201 The script.
  • 400 The body is not a valid JSON object, has unknown fields, or is too large.
  • 401 The platform API key is missing or wrong, or there is no valid admin panel session.
  • 404 No such object in this tenant (including objects of other tenants).
  • 409 The 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.
  • 422 A 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.
GET /v1/tenants/{tenantId}/accounts/{accountId}/sieve-scripts/{scriptId} team admins

Get one of an account's filter scripts

ParameterInType
tenantId *pathstring
accountId *pathstring
scriptId *pathinteger
Responses
  • 200 The script.
  • 401 The platform API key is missing or wrong, or there is no valid admin panel session.
  • 404 No such object in this tenant (including objects of other tenants).
PATCH /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.

ParameterInType
tenantId *pathstring
accountId *pathstring
scriptId *pathinteger
Responses
  • 200 The script.
  • 400 The body is not a valid JSON object, has unknown fields, or is too large.
  • 401 The platform API key is missing or wrong, or there is no valid admin panel session.
  • 404 No such object in this tenant (including objects of other tenants).
  • 409 The 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.
  • 422 A 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.
DELETE /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.

ParameterInType
tenantId *pathstring
accountId *pathstring
scriptId *pathinteger
Responses
  • 204 Deleted.
  • 401 The platform API key is missing or wrong, or there is no valid admin panel session.
  • 404 No such object in this tenant (including objects of other tenants).
  • 409 The 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.
GET /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.

ParameterInType
tenantId *pathstring
domain *pathstring
Responses
  • 200 The rules; an empty script means none.
  • 401 The platform API key is missing or wrong, or there is no valid admin panel session.
  • 404 No such object in this tenant (including objects of other tenants).
PUT /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.

ParameterInType
tenantId *pathstring
domain *pathstring
Responses
  • 200 The rules as saved.
  • 400 The body is not a valid JSON object, has unknown fields, or is too large.
  • 401 The platform API key is missing or wrong, or there is no valid admin panel session.
  • 404 No such object in this tenant (including objects of other tenants).
  • 422 A 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.
GET /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.

ParameterInType
tenantId *pathstring
Responses
  • 200 The rules; an empty script means none.
  • 401 The platform API key is missing or wrong, or there is no valid admin panel session.
  • 404 No such object in this tenant (including objects of other tenants).
PUT /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.

ParameterInType
tenantId *pathstring
Responses
  • 200 The rules as saved.
  • 400 The body is not a valid JSON object, has unknown fields, or is too large.
  • 401 The platform API key is missing or wrong, or there is no valid admin panel session.
  • 404 No such object in this tenant (including objects of other tenants).
  • 422 A 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

GET /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.

ParameterInType
tenantId *pathstring
Responses
  • 200 Queue entries, oldest message first (at most 1000).
  • 401 The platform API key is missing or wrong, or there is no valid admin panel session.
  • 404 No such object in this tenant (including objects of other tenants).

Plans

GET /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.

Responses
  • 200 The catalogue.
  • 401 The platform API key is missing or wrong, or there is no valid admin panel session.
GET /v1/plans

List plans

Responses
  • 200 Plans by sort order.
  • 401 The platform API key is missing or wrong, or there is no valid admin panel session.
POST /v1/plans

Add a plan

slug, name, kind and every limit are required. Free and solo plans have exactly one user.

Responses
  • 201 Created.
  • 400 The body is not a valid JSON object, has unknown fields, or is too large.
  • 401 The platform API key is missing or wrong, or there is no valid admin panel session.
  • 409 The 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.
  • 422 A 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.
GET /v1/plans/{plan}

Get a plan

ParameterInType
plan *pathstringThe plan's slug.
Responses
  • 200 The plan.
  • 401 The platform API key is missing or wrong, or there is no valid admin panel session.
  • 404 No such object in this tenant (including objects of other tenants).
PATCH /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.

ParameterInType
plan *pathstringThe plan's slug.
Responses
  • 200 The updated plan.
  • 400 The body is not a valid JSON object, has unknown fields, or is too large.
  • 401 The platform API key is missing or wrong, or there is no valid admin panel session.
  • 404 No such object in this tenant (including objects of other tenants).
  • 422 A 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.
DELETE /v1/plans/{plan}

Remove a plan no tenant is on

ParameterInType
plan *pathstringThe plan's slug.
Responses
  • 204 Removed.
  • 401 The platform API key is missing or wrong, or there is no valid admin panel session.
  • 404 No such object in this tenant (including objects of other tenants).
  • 409 The 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

GET /v1/abuse-settings

Captcha and signup address checks

Secrets show only as captchaSecretSet and ipqsKeySet.

Responses
  • 200 The settings.
  • 401 The platform API key is missing or wrong, or there is no valid admin panel session.
PATCH /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.

Request
{
  "action": "review",
  "captchaProvider": "pow",
  "ipqsEnabled": true,
  "ipqsKey": "...",
  "ipqsMaxScore": 85
}
Responses
  • 200 The new settings.
  • 401 The platform API key is missing or wrong, or there is no valid admin panel session.
  • 422 A 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.
POST /v1/abuse-settings/test

Run the signup checks on an address

Responses
  • 200 What 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"
    }
  • 401 The platform API key is missing or wrong, or there is no valid admin panel session.
  • 422 A 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.
GET /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.

ParameterInType
limitqueryinteger
Responses
  • 200 Complaints 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"
        }
      ]
    }
  • 401 The platform API key is missing or wrong, or there is no valid admin panel session.
GET /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.

Responses
  • 200 The 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"
        }
      ]
    }
  • 401 The platform API key is missing or wrong, or there is no valid admin panel session.
  • 403 A team admin asked for something only the platform operator may do, or for another team, or the CSRF token is missing or wrong.
  • 404 No such object in this tenant (including objects of other tenants).
POST /v1/domain-blocks

Block a domain

Request
{
  "action": "reject",
  "kind": "named",
  "note": "Links in the invoice scam",
  "value": "phish.example"
}
Responses
  • 201 The block.
  • 401 The platform API key is missing or wrong, or there is no valid admin panel session.
  • 403 A team admin asked for something only the platform operator may do, or for another team, or the CSRF token is missing or wrong.
  • 404 No such object in this tenant (including objects of other tenants).
  • 409 The 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.
  • 422 A 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.
DELETE /v1/domain-blocks/{blockId}

Remove a domain block

ParameterInType
blockId *pathinteger
Responses
  • 204 Removed.
  • 401 The platform API key is missing or wrong, or there is no valid admin panel session.
  • 403 A team admin asked for something only the platform operator may do, or for another team, or the CSRF token is missing or wrong.
  • 404 No such object in this tenant (including objects of other tenants).
GET /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.

Responses
  • 200 The settings.
    {
      "address": "fbl@mailo.city",
      "minSent": 200,
      "pausePermille": 3
    }
  • 401 The platform API key is missing or wrong, or there is no valid admin panel session.
PATCH /v1/feedback-settings

Change the feedback loop's settings

Request
{
  "address": "fbl@mailo.city",
  "pausePermille": 3
}
Responses
  • 200 The settings.
  • 401 The platform API key is missing or wrong, or there is no valid admin panel session.
  • 403 A team admin asked for something only the platform operator may do, or for another team, or the CSRF token is missing or wrong.
  • 422 A 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.
GET /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.

Responses
  • 200 The settings.
    {
      "delaySeconds": 300,
      "ipAllowlist": [
        "198.51.100.0/24",
        "2001:db8:1::/48"
      ],
      "passDays": 36,
      "retryHours": 24
    }
  • 401 The platform API key is missing or wrong, or there is no valid admin panel session.
PATCH /v1/greylist-settings

Change greylisting times and the platform's IP allowlist

Request
{
  "ipAllowlist": [
    "192.0.2.10",
    "198.51.100.0/24"
  ]
}
Responses
  • 200 The settings.
  • 401 The platform API key is missing or wrong, or there is no valid admin panel session.
  • 403 A team admin asked for something only the platform operator may do, or for another team, or the CSRF token is missing or wrong.
  • 422 A 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.
GET /v1/legal-exports

The audit log of legal exports, newest first

Responses
  • 200 The 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..."
        }
      ]
    }
  • 401 The platform API key is missing or wrong, or there is no valid admin panel session.
GET /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.

ParameterInType
qquerystringA message id, an address, an IP, or text in the line.
logquerystring
sincequerystring
untilquerystring
beforequeryintegerPage by line id (the previous answer's next).
limitqueryinteger
Responses
  • 200 The 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
    }
  • 400 The body is not a valid JSON object, has unknown fields, or is too large.
  • 401 The platform API key is missing or wrong, or there is no valid admin panel session.
GET /v1/mail-log-settings

How long the mail log is kept for searching

Responses
  • 200 The settings.
    {
      "days": 30
    }
  • 401 The platform API key is missing or wrong, or there is no valid admin panel session.
PATCH /v1/mail-log-settings

Change how long the mail log is kept

The log files are not touched; rotate them with logrotate.

Request
{
  "days": 90
}
Responses
  • 200 The settings.
  • 401 The platform API key is missing or wrong, or there is no valid admin panel session.
  • 403 A team admin asked for something only the platform operator may do, or for another team, or the CSRF token is missing or wrong.
  • 422 A 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.
GET /v1/mta-sts-settings

MTA-STS defaults and how sent mail follows other domains' policies

Responses
  • 200 The settings.
    {
      "defaultMaxAge": 604800,
      "defaultMode": "testing",
      "outbound": "follow",
      "updatedAt": "2026-10-08T12:00:00Z"
    }
  • 401 The platform API key is missing or wrong, or there is no valid admin panel session.
PATCH /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.

Request
{
  "defaultMode": "enforce"
}
Responses
  • 200 The new settings.
  • 401 The platform API key is missing or wrong, or there is no valid admin panel session.
  • 422 A 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.
GET /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.

Responses
  • 200 The setting.
    {
      "accountId": "0b6e4c1e-9a0e-4c55-8f0b-3d1f6c1d2e3f",
      "address": "noreply@mailo.city",
      "hasJmapToken": true,
      "hasSmtpPassword": false,
      "jmapUrl": "",
      "mode": "jmap",
      "smtpHost": "",
      "smtpPort": 587,
      "smtpSecurity": "starttls",
      "smtpUsername": ""
    }
  • 401 The platform API key is missing or wrong, or there is no valid admin panel session.
PATCH /v1/notice-mail

Change how Mailocity sends its notices

Responses
  • 200 The setting.
  • 401 The platform API key is missing or wrong, or there is no valid admin panel session.
  • 403 A team admin asked for something only the platform operator may do, or for another team, or the CSRF token is missing or wrong.
  • 422 A 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.
POST /v1/notice-mail/test

Send a test notice

Responses
  • 200 Whether it went, and the error if not.
    {
      "error": "signing in to mailo.city: 535 authentication failed",
      "ok": false
    }
  • 401 The platform API key is missing or wrong, or there is no valid admin panel session.
  • 422 A 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.
GET /v1/security-settings

Lockout, sign-in history and alert settings

Responses
  • 200 The settings.
  • 401 The platform API key is missing or wrong, or there is no valid admin panel session.
PATCH /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.

Request
{
  "historyDays": 180,
  "lockoutMinutes": 30,
  "lockoutThreshold": 8
}
Responses
  • 200 The new settings.
  • 401 The platform API key is missing or wrong, or there is no valid admin panel session.
  • 422 A 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.
GET /v1/settings

Platform-wide signup and webmail settings

Responses
  • 200 The settings.
  • 401 The platform API key is missing or wrong, or there is no valid admin panel session.
PATCH /v1/settings

Change signups, their limits and the webmail host

Fields left out keep their value. reservedLocalParts replaces the whole list.

Responses
  • 200 The updated settings.
  • 400 The body is not a valid JSON object, has unknown fields, or is too large.
  • 401 The platform API key is missing or wrong, or there is no valid admin panel session.
  • 422 A 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.
GET /v1/shared-domains

List the platform's shared domains

Accounts of any tenant can have addresses on a shared domain; free mail uses them.

Responses
  • 200 Shared domains by name.
  • 401 The platform API key is missing or wrong, or there is no valid admin panel session.
POST /v1/shared-domains

Add a shared domain

Responses
  • 201 Created.
  • 400 The body is not a valid JSON object, has unknown fields, or is too large.
  • 401 The platform API key is missing or wrong, or there is no valid admin panel session.
  • 409 The 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.
  • 422 A 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.
GET /v1/shared-domains/{domain}

Get a shared domain

ParameterInType
domain *pathstring
Responses
  • 200 The domain.
  • 401 The platform API key is missing or wrong, or there is no valid admin panel session.
  • 404 No such object in this tenant (including objects of other tenants).
PATCH /v1/shared-domains/{domain}

Change a shared domain's settings

Fields left out keep their value. Takes the same mail settings as a tenant's domain, plus signup.

ParameterInType
domain *pathstring
Responses
  • 200 The updated domain.
  • 400 The body is not a valid JSON object, has unknown fields, or is too large.
  • 401 The platform API key is missing or wrong, or there is no valid admin panel session.
  • 404 No such object in this tenant (including objects of other tenants).
  • 422 A 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.
DELETE /v1/shared-domains/{domain}

Remove a shared domain nobody has an address on

ParameterInType
domain *pathstring
Responses
  • 204 Removed.
  • 401 The platform API key is missing or wrong, or there is no valid admin panel session.
  • 404 No such object in this tenant (including objects of other tenants).
  • 409 The 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.
GET /v1/shared-domains/{domain}/bimi

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.

ParameterInType
domain *pathstring
Responses
  • 200 The 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"
    }
  • 401 The platform API key is missing or wrong, or there is no valid admin panel session.
  • 403 A team admin asked for something only the platform operator may do, or for another team, or the CSRF token is missing or wrong.
  • 404 No such object in this tenant (including objects of other tenants).
PUT /v1/shared-domains/{domain}/bimi

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.

ParameterInType
domain *pathstring
Request
{
  "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"
}
Responses
  • 200 The logo's state.
  • 400 The body is not a valid JSON object, has unknown fields, or is too large.
  • 401 The platform API key is missing or wrong, or there is no valid admin panel session.
  • 403 A team admin asked for something only the platform operator may do, or for another team, or the CSRF token is missing or wrong.
  • 404 No such object in this tenant (including objects of other tenants).
  • 422 A 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.
DELETE /v1/shared-domains/{domain}/bimi

Remove the domain's BIMI logo

Remove the default._bimi record too; receivers that find it then get no logo.

ParameterInType
domain *pathstring
Responses
  • 204 Removed.
  • 401 The platform API key is missing or wrong, or there is no valid admin panel session.
  • 403 A team admin asked for something only the platform operator may do, or for another team, or the CSRF token is missing or wrong.
  • 404 No such object in this tenant (including objects of other tenants).
GET /v1/shared-domains/{domain}/bimi-check

Check the published BIMI record

Looks up default._bimi in DNS and compares it with what it should be.

ParameterInType
domain *pathstring
Responses
  • 200 The 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="
    }
  • 401 The platform API key is missing or wrong, or there is no valid admin panel session.
  • 403 A team admin asked for something only the platform operator may do, or for another team, or the CSRF token is missing or wrong.
  • 404 No such object in this tenant (including objects of other tenants).
  • 409 The 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.
GET /v1/shared-domains/{domain}/dkim

List the DKIM keys of a shared domain

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.

ParameterInType
domain *pathstring
Responses
  • 200 Keys.
  • 401 The platform API key is missing or wrong, or there is no valid admin panel session.
  • 404 No such object in this tenant (including objects of other tenants).
POST /v1/shared-domains/{domain}/dkim

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.

ParameterInType
domain *pathstring
Request
{
  "algorithm": "rsa"
}
Responses
  • 201 The key.
  • 400 The body is not a valid JSON object, has unknown fields, or is too large.
  • 401 The platform API key is missing or wrong, or there is no valid admin panel session.
  • 404 No such object in this tenant (including objects of other tenants).
  • 422 A 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.
DELETE /v1/shared-domains/{domain}/dkim/{selector}

Delete a pending or retired key

The active key cannot be deleted (409); activate another first. Remove the TXT record afterwards.

ParameterInType
domain *pathstring
selector *pathstring
Responses
  • 204 Deleted.
  • 401 The platform API key is missing or wrong, or there is no valid admin panel session.
  • 404 No such object in this tenant (including objects of other tenants).
  • 409 The 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.
POST /v1/shared-domains/{domain}/dkim/{selector}/activate

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.

ParameterInType
domain *pathstring
selector *pathstring
Responses
  • 200 The key, now active.
  • 401 The platform API key is missing or wrong, or there is no valid admin panel session.
  • 404 No such object in this tenant (including objects of other tenants).
GET /v1/shared-domains/{domain}/dns

DNS records a shared domain must publish

ParameterInType
domain *pathstring
formatquerystring`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.
Responses
  • 200 Records 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"
  • 400 The body is not a valid JSON object, has unknown fields, or is too large.
  • 401 The platform API key is missing or wrong, or there is no valid admin panel session.
  • 404 No such object in this tenant (including objects of other tenants).
GET /v1/shared-domains/{domain}/mta-sts-check

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.

ParameterInType
domain *pathstring
Responses
  • 200 What was found; an empty `problems` means all is well.
    {
      "fetched": true,
      "maxAge": 604800,
      "mode": "enforce",
      "mx": [
        "mx.example.com"
      ],
      "problems": [],
      "recordFound": true,
      "recordId": "20261008T120000"
    }
  • 401 The platform API key is missing or wrong, or there is no valid admin panel session.
  • 404 No such object in this tenant (including objects of other tenants).
GET /v1/spam-reports

Spam and phishing reports from every tenant, newest first

Act on one through its tenant's /spam-reports/{reportId} routes.

ParameterInType
statusquerystring
limitqueryinteger
Responses
  • 200 The reports.
  • 400 The body is not a valid JSON object, has unknown fields, or is too large.
  • 401 The platform API key is missing or wrong, or there is no valid admin panel session.
GET /v1/spam-settings

The Rspamd connection and the platform's spam thresholds

The password is never returned; passwordSet says whether one is stored.

Responses
  • 200 The settings.
  • 401 The platform API key is missing or wrong, or there is no valid admin panel session.
PATCH /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.

Request
{
  "enabled": true,
  "junkScore": 6,
  "password": "controller password",
  "rejectScore": 15,
  "url": "http://127.0.0.1:11334"
}
Responses
  • 200 The new settings.
  • 400 The body is not a valid JSON object, has unknown fields, or is too large.
  • 401 The platform API key is missing or wrong, or there is no valid admin panel session.
  • 422 A 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.
POST /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.

Responses
  • 200 What was found; `error` says what failed.
    {
      "millis": 3,
      "passwordOk": true,
      "reachable": true,
      "version": "3.9.1"
    }
  • 401 The platform API key is missing or wrong, or there is no valid admin panel session.
GET /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.

Responses
  • 200 The settings.
    {
      "adminPasswordSignin": true,
      "clientId": "mailocity",
      "enabled": true,
      "hasClientSecret": true,
      "issuer": "https://zappo.city",
      "webmailPasswordSignin": true
    }
  • 401 The platform API key is missing or wrong, or there is no valid admin panel session.
PATCH /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.

Request
{
  "webmailPasswordSignin": false
}
Responses
  • 200 The settings.
  • 401 The platform API key is missing or wrong, or there is no valid admin panel session.
  • 403 A team admin asked for something only the platform operator may do, or for another team, or the CSRF token is missing or wrong.
  • 422 A 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.
POST /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.

Responses
  • 200 The settings.
  • 401 The platform API key is missing or wrong, or there is no valid admin panel session.
  • 403 A team admin asked for something only the platform operator may do, or for another team, or the CSRF token is missing or wrong.
  • 503 Zappocity is not in this process.
GET /v1/staff

The panel's administrators and their permissions

Responses
  • 200 Staff, 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"
      ]
    }
  • 401 The platform API key is missing or wrong, or there is no valid admin panel session.
  • 403 A team admin asked for something only the platform operator may do, or for another team, or the CSRF token is missing or wrong.
POST /v1/staff

Add a panel administrator

Request
{
  "email": "help@example.com",
  "name": "Helper",
  "password": "a long password",
  "permissions": [
    "read",
    "tenants",
    "reports",
    "impersonate"
  ]
}
Responses
  • 201 Added.
  • 401 The platform API key is missing or wrong, or there is no valid admin panel session.
  • 403 A team admin asked for something only the platform operator may do, or for another team, or the CSRF token is missing or wrong.
  • 409 The 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.
  • 422 A 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.
PATCH /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.

ParameterInType
staffId *pathstring
Responses
  • 200 Changed.
  • 401 The platform API key is missing or wrong, or there is no valid admin panel session.
  • 403 A team admin asked for something only the platform operator may do, or for another team, or the CSRF token is missing or wrong.
  • 404 No such object in this tenant (including objects of other tenants).
  • 409 The 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.
  • 422 A 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.
GET /v1/tenants/{tenantId}/complaints team admins

A team's complaints and complaint rates

As /v1/complaints, for one team; its admins see their own.

ParameterInType
tenantId *pathstring
limitqueryinteger
Responses
  • 200 Complaints and rates.
  • 401 The platform API key is missing or wrong, or there is no valid admin panel session.
  • 403 A team admin asked for something only the platform operator may do, or for another team, or the CSRF token is missing or wrong.
  • 404 No such object in this tenant (including objects of other tenants).
GET /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.

ParameterInType
tenantId *pathstring
Responses
  • 200 The 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"
        }
      ]
    }
  • 401 The platform API key is missing or wrong, or there is no valid admin panel session.
  • 403 A team admin asked for something only the platform operator may do, or for another team, or the CSRF token is missing or wrong.
  • 404 No such object in this tenant (including objects of other tenants).
POST /v1/tenants/{tenantId}/domain-blocks team admins

Block a domain

ParameterInType
tenantId *pathstring
Request
{
  "action": "reject",
  "kind": "named",
  "note": "Links in the invoice scam",
  "value": "phish.example"
}
Responses
  • 201 The block.
  • 401 The platform API key is missing or wrong, or there is no valid admin panel session.
  • 403 A team admin asked for something only the platform operator may do, or for another team, or the CSRF token is missing or wrong.
  • 404 No such object in this tenant (including objects of other tenants).
  • 409 The 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.
  • 422 A 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.
DELETE /v1/tenants/{tenantId}/domain-blocks/{blockId} team admins

Remove a domain block

ParameterInType
tenantId *pathstring
blockId *pathinteger
Responses
  • 204 Removed.
  • 401 The platform API key is missing or wrong, or there is no valid admin panel session.
  • 403 A team admin asked for something only the platform operator may do, or for another team, or the CSRF token is missing or wrong.
  • 404 No such object in this tenant (including objects of other tenants).
GET /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.

ParameterInType
tenantId *pathstring
Responses
  • 200 The networks.
    {
      "items": [
        "203.0.113.10/32",
        "198.51.100.0/24"
      ]
    }
  • 401 The platform API key is missing or wrong, or there is no valid admin panel session.
  • 403 A team admin asked for something only the platform operator may do, or for another team, or the CSRF token is missing or wrong.
  • 404 No such object in this tenant (including objects of other tenants).
PUT /v1/tenants/{tenantId}/ip-allowlist team admins

Replace a team's IP allowlist

ParameterInType
tenantId *pathstring
Request
{
  "items": [
    "203.0.113.10",
    "198.51.100.0/24"
  ]
}
Responses
  • 200 The networks
  • 401 The platform API key is missing or wrong, or there is no valid admin panel session.
  • 403 A team admin asked for something only the platform operator may do, or for another team, or the CSRF token is missing or wrong.
  • 404 No such object in this tenant (including objects of other tenants).
  • 422 A 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.
GET /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.

ParameterInType
tenantId *pathstring
Responses
  • 200 The 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
    }
  • 401 The platform API key is missing or wrong, or there is no valid admin panel session.
  • 403 A team admin asked for something only the platform operator may do, or for another team, or the CSRF token is missing or wrong.
  • 404 No such object in this tenant (including objects of other tenants).
PUT /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.

ParameterInType
tenantId *pathstring
Request
{
  "quotaBytes": -1,
  "sendPerDay": -1,
  "sendPerHour": -1
}
Responses
  • 200 The limits
  • 401 The platform API key is missing or wrong, or there is no valid admin panel session.
  • 403 A team admin asked for something only the platform operator may do, or for another team, or the CSRF token is missing or wrong.
  • 404 No such object in this tenant (including objects of other tenants).
  • 422 A 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.
GET /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.

ParameterInType
tenantId *pathstring
qquerystringA message id, an address, an IP, or text in the line.
logquerystring
sincequerystring
untilquerystring
beforequeryintegerPage by line id (the previous answer's next).
limitqueryinteger
Responses
  • 200 The lines.
  • 400 The body is not a valid JSON object, has unknown fields, or is too large.
  • 401 The platform API key is missing or wrong, or there is no valid admin panel session.
  • 403 A team admin asked for something only the platform operator may do, or for another team, or the CSRF token is missing or wrong.
  • 404 No such object in this tenant (including objects of other tenants).

Support

GET /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.

ParameterInType
tenantId *pathstring
accountId *pathstring
Responses
  • 200 Newest first.
    {
      "items": [
        {
          "author": "help@example.com",
          "body": "Sent spam on 8 Oct; watch.",
          "createdAt": "2026-10-08T12:00:00Z",
          "id": 3,
          "scope": "staff"
        }
      ]
    }
  • 401 The platform API key is missing or wrong, or there is no valid admin panel session.
  • 403 A team admin asked for something only the platform operator may do, or for another team, or the CSRF token is missing or wrong.
  • 404 No such object in this tenant (including objects of other tenants).
POST /v1/tenants/{tenantId}/accounts/{accountId}/notes team admins

Add an internal note on a user

ParameterInType
tenantId *pathstring
accountId *pathstring
Responses
  • 201 Added.
  • 401 The platform API key is missing or wrong, or there is no valid admin panel session.
  • 403 A team admin asked for something only the platform operator may do, or for another team, or the CSRF token is missing or wrong.
  • 404 No such object in this tenant (including objects of other tenants).
  • 422 A 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.
GET /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.

ParameterInType
tenantId *pathstring
statusquerystringactive: open and pending.
kindquerystring
limitqueryinteger
accountIdquerystring
Responses
  • 200 The tickets.
  • 401 The platform API key is missing or wrong, or there is no valid admin panel session.
  • 403 A team admin asked for something only the platform operator may do, or for another team, or the CSRF token is missing or wrong.
  • 404 No such object in this tenant (including objects of other tenants).
POST /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.

ParameterInType
tenantId *pathstring
Request
{
  "accountId": "3d05579f-ed2b-40c4-a5a7-5a3717b216ec",
  "body": "We saw 4000 messages in an hour.",
  "kind": "abuse",
  "subject": "Spam sent from your account"
}
Responses
  • 201 The ticket.
  • 401 The platform API key is missing or wrong, or there is no valid admin panel session.
  • 403 A team admin asked for something only the platform operator may do, or for another team, or the CSRF token is missing or wrong.
  • 404 No such object in this tenant (including objects of other tenants).
  • 422 A 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.
GET /v1/tenants/{tenantId}/tickets/{ticketId} team admins

A ticket and its messages

Team admins see replies and team notes; staff also see staff notes.

ParameterInType
tenantId *pathstring
ticketId *pathinteger
Responses
  • 200 The ticket and messages, oldest first.
  • 401 The platform API key is missing or wrong, or there is no valid admin panel session.
  • 403 A team admin asked for something only the platform operator may do, or for another team, or the CSRF token is missing or wrong.
  • 404 No such object in this tenant (including objects of other tenants).
PATCH /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.

ParameterInType
tenantId *pathstring
ticketId *pathinteger
Responses
  • 200 The ticket.
  • 401 The platform API key is missing or wrong, or there is no valid admin panel session.
  • 403 A team admin asked for something only the platform operator may do, or for another team, or the CSRF token is missing or wrong.
  • 404 No such object in this tenant (including objects of other tenants).
  • 422 A 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.
POST /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.

ParameterInType
tenantId *pathstring
ticketId *pathinteger
Responses
  • 204 Added.
  • 401 The platform API key is missing or wrong, or there is no valid admin panel session.
  • 403 A team admin asked for something only the platform operator may do, or for another team, or the CSRF token is missing or wrong.
  • 404 No such object in this tenant (including objects of other tenants).
  • 422 A 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.
GET /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.

ParameterInType
statusquerystringactive: open and pending.
kindquerystring
limitqueryinteger
queuequerystring
allquerystringAny value lists every queue.
Responses
  • 200 The tickets.
  • 401 The platform API key is missing or wrong, or there is no valid admin panel session.
  • 403 A team admin asked for something only the platform operator may do, or for another team, or the CSRF token is missing or wrong.
  • 404 No such object in this tenant (including objects of other tenants).

Tenants

GET /v1/tenants

List tenants

ParameterInType
externalRefquerystringReturn only the tenant with this external reference.
Responses
  • 200 Tenants, oldest first.
  • 401 The platform API key is missing or wrong, or there is no valid admin panel session.
POST /v1/tenants

Create a tenant

Responses
  • 201 Created.
  • 400 The body is not a valid JSON object, has unknown fields, or is too large.
  • 401 The platform API key is missing or wrong, or there is no valid admin panel session.
  • 409 The 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.
  • 422 A 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.
GET /v1/tenants/{tenantId} team admins

Get a tenant

ParameterInType
tenantId *pathstring
Responses
  • 200 The tenant.
  • 401 The platform API key is missing or wrong, or there is no valid admin panel session.
  • 404 No such object in this tenant (including objects of other tenants).
PATCH /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.

ParameterInType
tenantId *pathstring
Responses
  • 200 The updated tenant.
  • 400 The body is not a valid JSON object, has unknown fields, or is too large.
  • 401 The platform API key is missing or wrong, or there is no valid admin panel session.
  • 404 No such object in this tenant (including objects of other tenants).
  • 422 A 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.
GET /v1/tenants/{tenantId}/api-keys team admins

The team's API keys (never the keys themselves)

ParameterInType
tenantId *pathstring
Responses
  • 200 The keys.
  • 401 The platform API key is missing or wrong, or there is no valid admin panel session.
  • 403 A team admin asked for something only the platform operator may do, or for another team, or the CSRF token is missing or wrong.
  • 404 No such object in this tenant (including objects of other tenants).
POST /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.

ParameterInType
tenantId *pathstring
Request
{
  "name": "HR sync"
}
Responses
  • 201 The key, shown this once.
  • 401 The platform API key is missing or wrong, or there is no valid admin panel session.
  • 403 A team admin asked for something only the platform operator may do, or for another team, or the CSRF token is missing or wrong.
  • 404 No such object in this tenant (including objects of other tenants).
  • 409 The 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.
  • 422 A 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.
DELETE /v1/tenants/{tenantId}/api-keys/{keyId} team admins

Revoke an API key

ParameterInType
tenantId *pathstring
keyId *pathstring
Responses
  • 204 Revoked; requests with it answer 401 at once.
  • 401 The platform API key is missing or wrong, or there is no valid admin panel session.
  • 403 A team admin asked for something only the platform operator may do, or for another team, or the CSRF token is missing or wrong.
  • 404 No such object in this tenant (including objects of other tenants).
POST /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.

ParameterInType
tenantId *pathstring
Responses
  • 200 The tenant.
  • 401 The platform API key is missing or wrong, or there is no valid admin panel session.
  • 404 No such object in this tenant (including objects of other tenants).
POST /v1/tenants/{tenantId}/reject

Reject a signup

The tenant is suspended and marked rejected; it cannot sign in or receive mail.

ParameterInType
tenantId *pathstring
Responses
  • 200 The tenant.
  • 401 The platform API key is missing or wrong, or there is no valid admin panel session.
  • 404 No such object in this tenant (including objects of other tenants).
GET /v1/tenants/{tenantId}/spam-policy team admins

The team's spam thresholds and sender lists

ParameterInType
tenantId *pathstring
Responses
  • 200 The policy; empty when none is set.
  • 401 The platform API key is missing or wrong, or there is no valid admin panel session.
  • 403 A team admin asked for something only the platform operator may do, or for another team, or the CSRF token is missing or wrong.
  • 404 No such object in this tenant (including objects of other tenants).
PUT /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.

ParameterInType
tenantId *pathstring
Request
{
  "allow": [
    "partner.example"
  ],
  "block": [
    "spammy.example",
    "someone@bad.example"
  ],
  "junkScore": 5,
  "rejectScore": 12
}
Responses
  • 200 The new policy.
  • 400 The body is not a valid JSON object, has unknown fields, or is too large.
  • 401 The platform API key is missing or wrong, or there is no valid admin panel session.
  • 403 A team admin asked for something only the platform operator may do, or for another team, or the CSRF token is missing or wrong.
  • 404 No such object in this tenant (including objects of other tenants).
  • 422 A 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.
GET /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.

ParameterInType
tenantId *pathstring
statusquerystring
limitqueryinteger
Responses
  • 200 The reports.
  • 400 The body is not a valid JSON object, has unknown fields, or is too large.
  • 401 The platform API key is missing or wrong, or there is no valid admin panel session.
  • 403 A team admin asked for something only the platform operator may do, or for another team, or the CSRF token is missing or wrong.
  • 404 No such object in this tenant (including objects of other tenants).
GET /v1/tenants/{tenantId}/spam-reports/{reportId} team admins

One report

ParameterInType
tenantId *pathstring
reportId *pathstring
Responses
  • 200 The report.
  • 401 The platform API key is missing or wrong, or there is no valid admin panel session.
  • 403 A team admin asked for something only the platform operator may do, or for another team, or the CSRF token is missing or wrong.
  • 404 No such object in this tenant (including objects of other tenants).
PATCH /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.

ParameterInType
tenantId *pathstring
reportId *pathstring
Request
{
  "note": "Blocked the sender's domain for the team.",
  "status": "resolved"
}
Responses
  • 200 The report.
  • 401 The platform API key is missing or wrong, or there is no valid admin panel session.
  • 403 A team admin asked for something only the platform operator may do, or for another team, or the CSRF token is missing or wrong.
  • 404 No such object in this tenant (including objects of other tenants).
  • 422 A 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.
GET /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.

ParameterInType
tenantId *pathstring
reportId *pathstring
Responses
  • 200 The message as received (up to 10 MiB).
  • 401 The platform API key is missing or wrong, or there is no valid admin panel session.
  • 403 A team admin asked for something only the platform operator may do, or for another team, or the CSRF token is missing or wrong.
  • 404 No such object in this tenant (including objects of other tenants).
POST /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.

ParameterInType
tenantId *pathstring
reportId *pathstring
Responses
  • 200 How many copies were moved, and the report.
    {
      "moved": 4,
      "report": {
        "id": "6f1e...",
        "kind": "phishing",
        "quarantined": 4
      }
    }
  • 401 The platform API key is missing or wrong, or there is no valid admin panel session.
  • 403 A team admin asked for something only the platform operator may do, or for another team, or the CSRF token is missing or wrong.
  • 404 No such object in this tenant (including objects of other tenants).
GET /v1/tenants/{tenantId}/team-settings team admins

The team's joining, welcome and page settings

ParameterInType
tenantId *pathstring
Responses
  • 200 The settings, with the join page's address.
  • 401 The platform API key is missing or wrong, or there is no valid admin panel session.
  • 403 A team admin asked for something only the platform operator may do, or for another team, or the CSRF token is missing or wrong.
  • 404 No such object in this tenant (including objects of other tenants).
PATCH /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.

ParameterInType
tenantId *pathstring
Request
{
  "accent": "#1a73e8",
  "signupDomain": "acme.com",
  "welcomeBody": "Hi {name}, your address is {address}.",
  "welcomeEnabled": true
}
Responses
  • 200 The new settings.
  • 400 The body is not a valid JSON object, has unknown fields, or is too large.
  • 401 The platform API key is missing or wrong, or there is no valid admin panel session.
  • 403 A team admin asked for something only the platform operator may do, or for another team, or the CSRF token is missing or wrong.
  • 404 No such object in this tenant (including objects of other tenants).
  • 422 A 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.
GET /v1/tenants/{tenantId}/usage team admins

What the tenant uses against its plan

ParameterInType
tenantId *pathstring
Responses
  • 200 Counts and the plan's limits.
  • 401 The platform API key is missing or wrong, or there is no valid admin panel session.
  • 404 No such object in this tenant (including objects of other tenants).

Vacation replies

GET /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.

ParameterInType
tenantId *pathstring
accountId *pathstring
Responses
  • 200 The vacation response.
  • 401 The platform API key is missing or wrong, or there is no valid admin panel session.
  • 404 No such object in this tenant (including objects of other tenants).
PUT /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.

ParameterInType
tenantId *pathstring
accountId *pathstring
Request
{
  "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"
}
Responses
  • 200 The vacation response as stored.
  • 400 The body is not a valid JSON object, has unknown fields, or is too large.
  • 401 The platform API key is missing or wrong, or there is no valid admin panel session.
  • 404 No such object in this tenant (including objects of other tenants).
  • 422 A 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.