Mailocity admin API

version 1.65.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; or, with person, the mailboxes that Zappocity account signs in to. Each has its team's name and, when linked, platformPersonId. For the Zappocity panel's Mailboxes and Customers pages; platform administrators only.

ParameterInType
qquerystring
personquerystringA Zappocity account's id: its mailboxes (q is then ignored).
limitqueryinteger
Responses
  • 200 The mailboxes, as GET .../accounts/{accountId} gives them.
    {
      "items": [
        {
          "address": "carol@acme.test",
          "displayName": "Carol",
          "id": "0b6e4c1e-9a0e-4c55-8f0b-3d1f6c1d2e3f",
          "platformPersonId": "6c1f0b7e-2d4a-4b8e-9f3c-1a2b3c4d5e6f",
          "role": "member",
          "status": "active",
          "tenantId": "4ee2971f-0fae-4230-8512-ddf1704a769d",
          "tenantName": "Acme"
        }
      ]
    }
  • 401 The platform API key is missing or wrong, or there is no valid admin panel session.
  • 422 person is not an id.
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.
DELETE /v1/tenants/{tenantId}/accounts/{accountId} team admins

Delete a mailbox

Schedules the mailbox and everything in it (mail, aliases, calendars, contacts, filters, sessions) to be deleted after the platform's deletionGraceDays; until then it works as before and POST .../restore cancels it. now=true deletes it at once (as does a grace period of 0). Refused while the mailbox or its team is under a legal hold, and for the team's last owner while others are on the team (except for staff). Team admins cannot delete an owner. The compliance archive keeps its copies, and GET /v1/tenants/{tenantId}/deleted-accounts keeps a line for it.

ParameterInType
tenantId *pathstring
accountId *pathstring
nowqueryboolean
Responses
  • 202 Scheduled.
    {
      "deleteAfter": "2026-10-23T12:00:00Z",
      "requestedBy": "ops@example.test"
    }
  • 204 Deleted.
  • 401 The platform API key is missing or wrong, or there is no valid admin panel session.
  • 403 A team admin asked to delete an owner.
  • 404 No such object in this tenant (including objects of other tenants).
  • 409 Under a legal hold
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).
POST /v1/tenants/{tenantId}/accounts/{accountId}/restore team admins

Cancel a mailbox's deletion

ParameterInType
tenantId *pathstring
accountId *pathstring
Responses
  • 200 The account, no longer to be 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).
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.
DELETE /v1/tenants/{tenantId}/accounts/{accountId}/zappocity-link

Take a mailbox away from its Zappocity account

The Zappocity account that signs in to the mailbox no longer reaches it with single sign-on, and the mailbox's webmail sessions end. Within a minute the account sync gives the mailbox a Zappocity account of its own address and password, as for any mailbox without one. For a team's mailbox linked to the wrong person (such as after a merge). Platform staff only.

ParameterInType
tenantId *pathstring
accountId *pathstring
Responses
  • 204 Unlinked.
  • 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 mailbox here
GET /v1/tenants/{tenantId}/address-books team admins

The address books the team owns

Every member sees them in webmail, in contacts apps (CardDAV, named "(team)") and in the team's JMAP account (teamAccountId). Team owners and admins change them and their contacts; members read them, and change their contacts too when memberAccess is write. The contacts themselves are edited in webmail, over JMAP or CardDAV. See docs/kb/contacts.md.

ParameterInType
tenantId *pathstring
Responses
  • 200 The books, with how many contacts each has.
    {
      "items": [
        {
          "contacts": 18,
          "description": "",
          "id": "A42",
          "memberAccess": "read",
          "name": "Suppliers"
        }
      ]
    }
  • 401 The platform API key is missing or wrong, or there is no valid admin panel session.
POST /v1/tenants/{tenantId}/address-books team admins

Make a team address book

ParameterInType
tenantId *pathstring
Request
{
  "memberAccess": "write",
  "name": "Suppliers"
}
Responses
  • 201 The book.
  • 401 The platform API key is missing or wrong, or there is no valid admin panel session.
  • 409 A book has this name, or the team has 50.
  • 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/tenants/{tenantId}/address-books/{bookId} team admins

Change a team address book

ParameterInType
tenantId *pathstring
bookId *pathstringThe book's JMAP id, such as A42.
Request
{
  "memberAccess": "read"
}
Responses
  • 204 Changed.
  • 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 A book has this name.
  • 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}/address-books/{bookId} team admins

Delete a team address book

Only an empty one, unless removeContents is true (in the query or a JSON body).

ParameterInType
tenantId *pathstring
bookId *pathstringThe book's JMAP id, such as A42.
removeContentsqueryboolean
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 book has contacts.
GET /v1/tenants/{tenantId}/deleted-accounts team admins

The team's deleted mailboxes

A line kept for each mailbox deleted, newest first.

ParameterInType
tenantId *pathstring
Responses
  • 200 The deleted mailboxes.
    {
      "items": [
        {
          "accountId": "0b8e5f43-2d6c-4c55-8f1e-7c9a3e2d1b00",
          "address": "old@acme.test",
          "deleteAfter": "2026-10-23T12:00:00Z",
          "deletedAt": "2026-10-23T12:30:00Z",
          "requestedBy": "amy@acme.test"
        }
      ]
    }
  • 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}/resources team admins

The team's bookable rooms and equipment

Each has an address on one of the team's domains and a calendar of its own. Inviting the address to an event books it: the invitation is accepted when the resource is open, the organizer may book it (whoCanBook), no occurrence in the next year is longer than maxMinutes and none overlaps another booking; otherwise it is declined with the reason. The answer goes back to the organizer as an iMIP reply. See docs/kb/rooms-and-equipment.md.

ParameterInType
tenantId *pathstring
Responses
  • 200 The resources.
    {
      "items": [
        {
          "address": "board@acme.com",
          "booking": "open",
          "capacity": 10,
          "createdAt": "2026-10-09T14:00:00Z",
          "description": "",
          "id": "6f1c2e8a-3b5d-4c7e-9f10-2a3b4c5d6e7f",
          "kind": "room",
          "location": "3rd floor",
          "maxMinutes": 240,
          "name": "Board room",
          "whoCanBook": "team"
        }
      ]
    }
  • 401 The platform API key is missing or wrong, or there is no valid admin panel session.
POST /v1/tenants/{tenantId}/resources team admins

Add a bookable room or piece of equipment

ParameterInType
tenantId *pathstring
Request
{
  "address": "board@acme.com",
  "capacity": 10,
  "location": "3rd floor",
  "maxMinutes": 240,
  "name": "Board room"
}
Responses
  • 201 The resource.
  • 401 The platform API key is missing or wrong, or there is no valid admin panel session.
  • 409 Another mailbox
  • 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/tenants/{tenantId}/resources/{resourceId} team admins

Change a room or piece of equipment

Fields left out stay; the address cannot change. Closing it declines new invitations; bookings already made stay.

ParameterInType
tenantId *pathstring
resourceId *pathstring
Request
{
  "booking": "closed"
}
Responses
  • 200 The resource.
  • 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}/resources/{resourceId} team admins

Delete a room or piece of equipment with its bookings

ParameterInType
tenantId *pathstring
resourceId *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).
GET /v1/tenants/{tenantId}/shared-mailboxes team admins

The team's shared mailboxes

Shared mailboxes (sales@, info@) are accounts of kind shared that no one signs in to. The teammates they are shared with (members, with read, write or send access) open them in webmail and IMAP; members with send access send as them. They take no seat; the plan's maxSharedMailboxes caps them. Members are managed with .../accounts/{accountId}/shares, and a shared mailbox is deleted like any account.

ParameterInType
tenantId *pathstring
Responses
  • 200 The shared mailboxes with their members.
    {
      "items": [
        {
          "address": "sales@acme.com",
          "displayName": "Sales",
          "id": "0b6e4c1e-9a0e-4c55-8f0b-3d1f6c1d2e3f",
          "kind": "shared",
          "members": [
            {
              "granteeAddress": "amy@acme.com",
              "granteeName": "Amy",
              "maySend": true,
              "mayWrite": true
            }
          ],
          "status": "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).
POST /v1/tenants/{tenantId}/shared-mailboxes team admins

Add a shared mailbox

On one of the team's own domains, within the plan's maxSharedMailboxes. Then share it with teammates (PUT .../accounts/{accountId}/shares/{grantee} with access read, write or send).

ParameterInType
tenantId *pathstring
Request
{
  "address": "sales@acme.com",
  "displayName": "Sales"
}
Responses
  • 201 The shared mailbox.
  • 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 address is taken, or the plan's limit is reached.
  • 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-roles team admins

The team's custom roles, and the permissions they can have

A role opens parts of the team manager to the members given it (teamRoleId on the account): users, aliases, domains, mail, support, settings. Such a member never acts on owners or admins, changes roles, manages roles, impersonates, or touches the archive and holds. Owners and admins only.

ParameterInType
tenantId *pathstring
Responses
  • 200 The roles.
    {
      "items": [
        {
          "id": "6f1c2a9e-1b0e-4f3a-9a52-2f4d1e0c7b11",
          "members": 2,
          "name": "Helpdesk",
          "permissions": [
            "users",
            "support"
          ]
        }
      ],
      "permissions": [
        {
          "key": "users",
          "name": "Users: add, change, suspend and delete mailboxes..."
        }
      ]
    }
  • 401 The platform API key is missing or wrong, or there is no valid admin panel session.
  • 403 Not an owner or admin.
POST /v1/tenants/{tenantId}/team-roles team admins

Make a custom role

ParameterInType
tenantId *pathstring
Request
{
  "name": "Helpdesk",
  "permissions": [
    "users",
    "support"
  ]
}
Responses
  • 201 The role.
  • 401 The platform API key is missing or wrong, or there is no valid admin panel session.
  • 409 A role with this name exists.
  • 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/tenants/{tenantId}/team-roles/{roleId} team admins

Rename a role or replace its permissions

ParameterInType
tenantId *pathstring
roleId *pathstring
Request
{
  "permissions": [
    "users",
    "aliases",
    "support"
  ]
}
Responses
  • 200 The role.
  • 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 A role with this name exists.
  • 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-roles/{roleId} team admins

Delete a role (its members keep their base role)

ParameterInType
tenantId *pathstring
roleId *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).
GET /v1/tenants/{tenantId}/templates team admins

The team's templates

Messages the team keeps for its members to start from or insert when writing, in webmail under Templates… (the most used first). Members' own templates stay messages in their Templates folder. Webmail cleans the HTML when a template is inserted, keeping only plain formatting. Team members with the templates permission manage them. See docs/kb/webmail.md#team-templates.

ParameterInType
tenantId *pathstring
Responses
  • 200 The templates.
    {
      "items": [
        {
          "createdAt": "2026-10-09T14:00:00Z",
          "createdBy": "lead@acme.com",
          "html": "\u003cp\u003eHi,\u003c/p\u003e\u003cp\u003eFollowing up on the quote we sent...\u003c/p\u003e",
          "id": "0b8e5d2c-1a3f-4e6d-9c7b-2f1e0d9c8b7a",
          "name": "Quote follow-up",
          "subject": "Your quote",
          "text": "",
          "updatedAt": "2026-10-09T14:00:00Z",
          "uses": 12
        }
      ]
    }
  • 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}/templates team admins

Add a team template

ParameterInType
tenantId *pathstring
Request
{
  "html": "\u003cp\u003eHi,\u003c/p\u003e\u003cp\u003eFollowing up on the quote we sent...\u003c/p\u003e",
  "name": "Quote follow-up",
  "subject": "Your quote"
}
Responses
  • 201 The template.
  • 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.
PUT /v1/tenants/{tenantId}/templates/{templateId} team admins

Change a team template

Replaces its name, subject and content.

ParameterInType
tenantId *pathstring
templateId *pathstring
Request
{
  "name": "Quote follow-up",
  "subject": "Your quote from Acme",
  "text": "Hi,\n\nFollowing up on the quote we sent."
}
Responses
  • 200 The template.
  • 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}/templates/{templateId} team admins

Delete a team template

ParameterInType
tenantId *pathstring
templateId *pathstring
Responses
  • 204 Deleted.
  • 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).

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; a session signed in with Zappocity also signs out of zappo.city (and so everything signed in from there). 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.
DELETE /v1/tenants/{tenantId}/domains/{domain} team admins

Remove a domain with everything on it

Removes one of the tenant's own domains with every mailbox, alias, mailing list, room and setting on it. confirm must repeat the domain's name. The mailboxes are scheduled for deletion and the domain removed after the platform's deletion grace period (202, with when and which mailboxes); until then the domain works and DELETE .../domains/{domain}/removal keeps it. now=true (or a grace period of 0) removes it at once (204), and nothing can be restored. Refused (409) while a mailbox on it is under a legal hold, naming them. In the team manager only owners may, and not for the domain of their own mailbox. Records published for it elsewhere (Route53, dnso.city) are left as they are.

ParameterInType
tenantId *pathstring
domain *pathstring
confirm *querystringThe domain's name again.
nowquerybooleanRemove it at once instead of after the grace period.
Responses
  • 202 Scheduled.
    {
      "mailboxes": [
        "amy@example.com",
        "sales@example.com"
      ],
      "removeAfter": "2026-11-09T12:00:00Z"
    }
  • 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).
  • 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}/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-automation team admins

The domain's automatic DNS

Whether the domain's DNS records are published at its DNS provider (Route53), in which zone, with which access key (never the secret), and the last publish. See docs/kb/automatic-dns.md.

ParameterInType
tenantId *pathstring
domain *pathstring
Responses
  • 200 The automation, or null.
    {
      "automation": {
        "accessKeyId": "AKIAIOSFODNN7EXAMPLE",
        "domain": "acme.com",
        "includeOptional": true,
        "lastChanges": 6,
        "lastError": "",
        "lastSyncAt": "2026-10-09T14:00:00Z",
        "provider": "route53",
        "region": "us-east-1",
        "secretSet": true,
        "zone": "acme.com",
        "zoneId": ""
      }
    }
  • 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}/dns-automation team admins

Publish the domain's records automatically

The records of the domain's DNS list are published in the zone (the domain or one above it) and kept up to date hourly: MX, CNAME and SRV record sets become ours alone; among TXT values, ours replace their own kind (SPF, DMARC, the ownership record, DKIM keys at their selectors) and others at the same name stay. The secret is sealed with the server key and never shown; left out, the stored one is kept for the same access key. The setting is removed when the domain moves to another team. A team always gives its own key. With provider dnso, the records go to dnso.city instead (Zappocity's DNS hosting, free for a team's own domains): no keys; the zone is made in a DNS account of the team's organization (the one its Mailocity subscription is in) the first time, and the team's other records there stay. A team not linked to an organization, or a domain dnso.city serves for another organization, fails to publish with the reason.

ParameterInType
tenantId *pathstring
domain *pathstring
Responses
  • 200 The automation.
  • 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}/dns-automation team admins

Stop publishing (records already published stay)

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.
  • 404 No such object in this tenant (including objects of other tenants).
POST /v1/tenants/{tenantId}/domains/{domain}/dns-automation/sync team admins

Publish the records now

ParameterInType
tenantId *pathstring
domain *pathstring
Responses
  • 200 Whether it worked, and the record sets changed (none when all were right).
    {
      "changes": [
        {
          "name": "@",
          "type": "MX",
          "values": [
            "10 mx.mailo.city."
          ]
        }
      ],
      "ok": true
    }
  • 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).
DELETE /v1/tenants/{tenantId}/domains/{domain}/removal team admins

Keep a domain being removed

Cancels the domain's removal and the deletion of the mailboxes its removal scheduled (owners only in the team manager). Restoring one of those mailboxes keeps the domain too.

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).
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.

Forums

PATCH /v1/forum-posts/{postId}

Hide or show any post (staff)

A hidden post shows as hidden by staff; its text shows only to moderators.

ParameterInType
postId *pathinteger
Request
{
  "hidden": true
}
Responses
  • 204 Done.
  • 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/forum-topics/{topicId}

Pin, lock or hide any topic (staff)

ParameterInType
topicId *pathinteger
Request
{
  "hidden": false,
  "locked": true
}
Responses
  • 204 Done.
  • 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/forums

The platform's forums

Readable by anyone, at /forum; any mailbox may post (except in a closed forum, where only staff start topics).

Responses
  • 200 The forums.
    {
      "items": [
        {
          "closed": true,
          "createdAt": "2026-10-09T12:00:00Z",
          "description": "News from Mailocity",
          "enabled": true,
          "id": "6f1c2a9e-1b0e-4f3a-9a52-2f4d1e0c7b11",
          "lastPostAt": "2026-10-09T12:00:00Z",
          "name": "Announcements",
          "slug": "announcements",
          "sortOrder": 10,
          "tenantId": null,
          "topics": 3,
          "visibility": "public"
        }
      ]
    }
  • 401 The platform API key is missing or wrong, or there is no valid admin panel session.
POST /v1/forums

Make a platform forum

Request
{
  "closed": true,
  "description": "News from Mailocity",
  "name": "Announcements",
  "slug": "announcements",
  "sortOrder": 10
}
Responses
  • 201 The forum.
  • 401 The platform API key is missing or wrong, or there is no valid admin panel session.
  • 409 A forum with this slug exists.
  • 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/forums/{forumId}

Change a platform forum

ParameterInType
forumId *pathstring
Request
{
  "closed": false
}
Responses
  • 200 The forum.
  • 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 A forum with this slug exists.
  • 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/forums/{forumId}

Delete a platform forum with all its topics

ParameterInType
forumId *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).
GET /v1/tenants/{tenantId}/forums team admins

A team's forums

enabled says whether the team's plan includes forums; without it they are kept but not shown.

ParameterInType
tenantId *pathstring
Responses
  • 200 The forums.
    {
      "enabled": true,
      "items": [
        {
          "closed": false,
          "description": "",
          "enabled": true,
          "id": "6f1c2a9e-1b0e-4f3a-9a52-2f4d1e0c7b11",
          "name": "General",
          "slug": "general",
          "sortOrder": 100,
          "team": "acme",
          "tenantId": "3c1d9a7e-55b2-4b8e-9e1f-0a2b3c4d5e6f",
          "topics": 12,
          "visibility": "members"
        }
      ]
    }
  • 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}/forums team admins

Make a team forum

Needs the forums feature on the team's plan. visibility members (the default) is for the team's members alone; public lets anyone read it (only members post). The team's owners and admins moderate it in the forum (pin, lock, hide).

ParameterInType
tenantId *pathstring
Request
{
  "description": "Anything about work",
  "name": "General",
  "slug": "general"
}
Responses
  • 201 The forum.
  • 401 The platform API key is missing or wrong, or there is no valid admin panel session.
  • 403 The team's plan does not include forums.
  • 409 A forum with this slug exists.
  • 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/tenants/{tenantId}/forums/{forumId} team admins

Change a team forum

ParameterInType
tenantId *pathstring
forumId *pathstring
Request
{
  "visibility": "public"
}
Responses
  • 200 The forum.
  • 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}/forums/{forumId} team admins

Delete a team forum with all its topics

ParameterInType
tenantId *pathstring
forumId *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).
GET /v1/tenants/{tenantId}/sites team admins

The team's own addresses for its forums, wiki, webmail and team manager

Its own names (null when unset), the target they point at with a CNAME (target, "" for the server's host name), the last check of each, and, when the platform sets teamSiteBase, <team>.f.<base> and <team>.d.<base>. See docs/kb/custom-host-names.md.

ParameterInType
tenantId *pathstring
Responses
  • 200 The addresses.
    {
      "baseForum": "acme.f.zappo.city",
      "baseWiki": "acme.d.zappo.city",
      "checks": {
        "forum.acme.com": {
          "checkedAt": "2026-10-09T14:00:00Z",
          "detail": "No CNAME to sites.mailo.city found",
          "ok": false
        },
        "mail.acme.com": {
          "checkedAt": "2026-10-09T14:00:00Z",
          "detail": "CNAME to sites.mailo.city",
          "ok": true
        }
      },
      "forumHost": "forum.acme.com",
      "mailHost": "mail.acme.com",
      "manageHost": null,
      "target": "sites.mailo.city",
      "wikiHost": null
    }
  • 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}/sites team admins

Set the team's own addresses

Each must be one of the team's proven domains or a name under one (forum.acme.com), and no other site's; "" removes it, and a field left out stays. Each name points at the target with a CNAME (or has the same addresses) and is checked at once, then every 30 minutes until right and daily after. The forum and wiki names show the team's public forums or public wiki; the webmail and team manager names serve them at their root, but only once a check finds them pointing here. A certificate is issued on the first visit.

ParameterInType
tenantId *pathstring
Request
{
  "forumHost": "forum.acme.com",
  "mailHost": "mail.acme.com"
}
Responses
  • 200 The addresses.
  • 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 Not on one of the team's proven domains, already in use, or the same for two sites.
POST /v1/tenants/{tenantId}/sites/check team admins

Check now whether the team's own addresses point here

ParameterInType
tenantId *pathstring
Responses
  • 200 The addresses with their checks
  • 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}/wiki-settings team admins

Who reads and edits the team's wiki

The wiki is at /wiki when the team's plan has the wiki feature (enabled).

ParameterInType
tenantId *pathstring
Responses
  • 200 The settings.
    {
      "editors": "members",
      "enabled": true,
      "public": false,
      "team": "acme",
      "tenantId": "3c1d9a7e-55b2-4b8e-9e1f-0a2b3c4d5e6f"
    }
  • 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}/wiki-settings team admins

Change who reads and edits the team's wiki

public lets anyone read it (only members edit); editors is members (any member but guests) or admins (owners and admins only). Owners and admins always delete pages.

ParameterInType
tenantId *pathstring
Request
{
  "editors": "admins",
  "public": true
}
Responses
  • 200 The settings.
  • 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.

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.

Mailing lists

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

The team's mailing lists

ParameterInType
tenantId *pathstring
Responses
  • 200 The lists, by address.
  • 401 The platform API key is missing or wrong, or there is no valid admin panel session.
POST /v1/tenants/{tenantId}/lists team admins

Make a mailing list

The address must be on one of the team's own domains (not a shared one) and not be a mailbox, alias or list already; it is then taken for mailboxes and aliases too. The plan's maxLists applies (409 with limit "lists").

ParameterInType
tenantId *pathstring
Request
{
  "address": "team@acme.com",
  "joining": "team",
  "kind": "discussion",
  "name": "Team",
  "subjectPrefix": "[team]"
}
Responses
  • 201 The list.
  • 401 The platform API key is missing or wrong, or there is no valid admin panel session.
  • 409 A mailbox, alias or list has the address, or the plan's limit is reached.
  • 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}/lists/{listId} team admins

A mailing list

ParameterInType
tenantId *pathstring
listId *pathstring
Responses
  • 200 The list.
  • 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}/lists/{listId} team admins

Change a mailing list

Fields left out stay as they are. The address cannot change.

ParameterInType
tenantId *pathstring
listId *pathstring
Request
{
  "others": "reject",
  "whoCanPost": "anyone"
}
Responses
  • 200 The list.
  • 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}/lists/{listId} team admins

Delete a mailing list with its members and held posts

ParameterInType
tenantId *pathstring
listId *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).
GET /v1/tenants/{tenantId}/lists/{listId}/held team admins

Posts waiting for a moderator

Posts from people who may not post (when others is hold) or that Rspamd marks as likely spam. Each is kept 14 days.

ParameterInType
tenantId *pathstring
listId *pathstring
Responses
  • 200 The held posts, 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).
DELETE /v1/tenants/{tenantId}/lists/{listId}/held/{heldId} team admins

Throw a held post away

ParameterInType
tenantId *pathstring
listId *pathstring
heldId *pathstring
Responses
  • 204 Discarded.
  • 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}/lists/{listId}/held/{heldId}/approve team admins

Send a held post to the members

ParameterInType
tenantId *pathstring
listId *pathstring
heldId *pathstring
Responses
  • 204 Queued for the members.
  • 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).
  • 503 The post could not be queued; it is held again.
GET /v1/tenants/{tenantId}/lists/{listId}/members team admins

A mailing list's members

With requests to join by mail not confirmed yet (pending) and each member's bounces.

ParameterInType
tenantId *pathstring
listId *pathstring
Responses
  • 200 The members, 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).
PUT /v1/tenants/{tenantId}/lists/{listId}/members/{address} team admins

Add a member or change one

Fields left out keep their value (a new member: not a moderator, receives). Putting a member again confirms a pending request and starts delivery again after bounces stopped it. The plan's maxListMembers applies (409 with limit "listMembers").

ParameterInType
tenantId *pathstring
listId *pathstring
address *pathstringAny address, here or elsewhere; a +tag files the posts into that folder for a mailbox here.
Request
{
  "moderator": true,
  "name": "Amy"
}
Responses
  • 200 The member.
  • 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 plan's limit of members is reached.
  • 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}/lists/{listId}/members/{address} team admins

Take an address off a mailing list

ParameterInType
tenantId *pathstring
listId *pathstring
address *pathstringAny address, here or elsewhere; a +tag files the posts into that folder for a mailbox here.
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).

Outbound mail

GET /v1/ip-pool

The outbound IPv4 addresses

Mail leaves from a pool address: one dedicated to the sender's team when it has any, otherwise a shared one, the same for a sending domain each time. Each address's hostname is its EHLO name and should be its PTR. See docs/kb/outbound-ips.md.

Responses
  • 200 The addresses.
    {
      "items": [
        {
          "address": "203.0.113.10",
          "createdAt": "2026-10-09T14:00:00Z",
          "enabled": true,
          "hostname": "o1.mailo.city",
          "kind": "shared",
          "note": "",
          "team": "",
          "tenantId": null
        }
      ]
    }
  • 401 The platform API key is missing or wrong, or there is no valid admin panel session.
POST /v1/ip-pool

Add an address of this server to the pool

Request
{
  "address": "203.0.113.11",
  "hostname": "acme.out.mailo.city",
  "kind": "dedicated",
  "tenantId": "6f1c2e8a-3b5d-4c7e-9f10-2a3b4c5d6e7f"
}
Responses
  • 201 The address.
  • 401 The platform API key is missing or wrong, or there is no valid admin panel session.
  • 409 The address is in the pool already.
  • 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/ip-pool/{address}

Change a pool address

Fields left out stay; tenantId "" takes a dedicated address from its team.

ParameterInType
address *pathstring
Request
{
  "enabled": false
}
Responses
  • 200 The 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).
  • 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/ip-pool/{address}

Take an address out of the pool

ParameterInType
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).
GET /v1/ip-settings

IPv6 and publishing settings for outbound addresses

With ipv6Prefix and ipv6NameBase set, every hosted domain gets an address of its own in the prefix (made from the domain's hash), named x<10 hex>.<ipv6NameBase>, and mail is sent over IPv6 first (preferIpv6) where the receiving server has an address. forwardZone and reverseZones are where names and PTR records are published (Route53, with the server's own credentials).

Responses
  • 200 The settings.
    {
      "forwardZone": "mailo.city",
      "ipv6NameBase": "out.mailo.city",
      "ipv6Prefix": "2001:db8:1:2::/64",
      "preferIpv6": true,
      "publishError": "",
      "publishedAt": "2026-10-09T14:00:00Z",
      "reverseZones": [
        "113.0.203.in-addr.arpa",
        "2.0.0.0.1.0.0.0.8.b.d.0.1.0.0.2.ip6.arpa"
      ],
      "updatedAt": "2026-10-09T14:00:00Z"
    }
  • 401 The platform API key is missing or wrong, or there is no valid admin panel session.
PATCH /v1/ip-settings

Change the IPv6 and publishing settings

A new prefix gives every domain a new address; their SPF records change with it.

Request
{
  "ipv6NameBase": "out.mailo.city",
  "ipv6Prefix": "2001:db8:1:2::/64"
}
Responses
  • 200 The 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/ip-settings/publish

Publish the addresses' names and PTR records

A (or AAAA) records for each name in forwardZone, and a PTR for each address in its reverse zone, through Route53 with the server's own credentials. Addresses without a reverse zone here are named in the error: their PTR is set by whoever provides them.

Responses
  • 200 Whether it worked, and the record sets changed.
    {
      "changes": [
        {
          "name": "10",
          "type": "PTR",
          "values": [
            "o1.mailo.city."
          ]
        }
      ],
      "error": "",
      "ok": true
    }
  • 401 The platform API key is missing or wrong, or there is no valid admin panel session.
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).
GET /v1/tenants/{tenantId}/sending-addresses team admins

Where the team's mail leaves from

ParameterInType
tenantId *pathstring
Responses
  • 200 Its IPv4 addresses (dedicated or shared) and each domain's address and IPv6.
    {
      "dedicated": true,
      "domains": [
        {
          "domain": "acme.com",
          "ipv4": "203.0.113.11",
          "ipv6": "2001:db8:1:2:9f3c:1a2b:4c5d:6e7f",
          "ipv6Name": "x1a2b3c4d5e.out.mailo.city"
        }
      ],
      "ipv4": [
        "203.0.113.11"
      ],
      "preferIpv6": true
    }
  • 401 The platform API key is missing or wrong, or there is no valid admin panel session.

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/access-blocks

The access blocks

Rules that refuse sign-up (Zappocity accounts, Mailocity sign-up, team join pages and sign-up codes), sign-in (Zappocity accounts and every mailbox sign-in: webmail, IMAP, SMTP, ManageSieve, DAV) and/or opening support tickets (the account page, webmail and mail to a help desk queue). Kinds: email (an address; +tags ignored), domain (the address's domain and its subdomains), ip (an address or network), asn and country (from the IPNetDB file under abuse settings; without it they match nothing), mx and ns (the mail or name servers of the address's domain, and hosts under them). DNS lookups that fail match nothing. networkLookups says whether asn and country rules can match. See docs/kb/access-blocks.md.

Responses
  • 200 The rules.
    {
      "actions": [
        "signup",
        "signin",
        "tickets"
      ],
      "items": [
        {
          "applies": [
            "signup",
            "tickets"
          ],
          "createdAt": "2026-10-09T12:00:00Z",
          "createdBy": "admin@example.com",
          "expiresAt": null,
          "hits": 41,
          "id": 3,
          "kind": "mx",
          "lastHitAt": "2026-10-09T15:02:00Z",
          "note": "Throwaway addresses",
          "value": "mail.disposable.example"
        }
      ],
      "kinds": [
        "email",
        "domain",
        "ip",
        "asn",
        "country",
        "mx",
        "ns"
      ],
      "networkLookups": true
    }
  • 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/access-blocks

Add an access block

Adds a rule, or replaces the one with the same kind and value. Values are normalized: an IP address becomes a /32 or /128 network, AS64500 becomes 64500, country codes are upper case. Needs the settings permission.

Request
{
  "applies": [
    "signup",
    "signin"
  ],
  "days": 30,
  "kind": "ip",
  "note": "Credential stuffing",
  "value": "203.0.113.0/24"
}
Responses
  • 201 The rule.
  • 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.
  • 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/access-blocks/test

Which rule would refuse this

The rule that would refuse the action for the address and IP address, or none. A match counts as a hit.

Request
{
  "action": "signup",
  "email": "someone@disposable.example",
  "ip": "203.0.113.9"
}
Responses
  • 200 The verdict.
    {
      "blocked": true,
      "rule": {
        "applies": [
          "signup",
          "tickets"
        ],
        "id": 3,
        "kind": "mx",
        "value": "mail.disposable.example"
      }
    }
  • 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.
  • 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/access-blocks/{blockId}

Remove an access 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/blog/posts

The blogs' posts

Every post, drafts and scheduled ones first, then by publish time; with site, only that site's. Published posts show at /blog on their site (the Zappocity blog also at /zappocity/blog), with a page each, tag pages and an Atom feed at /blog/feed.xml. See docs/kb/blog.md.

ParameterInType
sitequerystring
tagquerystring
Responses
  • 200 The posts.
    {
      "items": [
        {
          "authorEmail": "admin@example.com",
          "authorName": "Terrance",
          "body": "Team admins can now...",
          "createdAt": "2026-10-09T15:00:00Z",
          "id": "5a1d2c3b-4e5f-4a6b-8c7d-9e0f1a2b3c4d",
          "publishAt": "2026-10-10T09:00:00Z",
          "site": "mailocity",
          "slug": "shared-templates",
          "status": "published",
          "summary": "Write the same reply once.",
          "tags": [
            "webmail",
            "teams"
          ],
          "title": "Templates for the whole team",
          "updatedAt": "2026-10-09T15:00:00Z"
        }
      ],
      "sites": [
        "mailocity",
        "zappocity"
      ]
    }
  • 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.
POST /v1/blog/posts

Write a post

A draft, or published: at publishAt (which can be in the future, for a scheduled post) or now when it is left out. The body is Markdown; links may go to http(s), mail, this site's pages and anchors. Needs the content permission.

Request
{
  "authorName": "Terrance",
  "body": "Team admins can now keep **templates** for everyone.",
  "publishAt": "2026-10-10T09:00:00Z",
  "site": "mailocity",
  "slug": "shared-templates",
  "status": "published",
  "summary": "Write the same reply once.",
  "tags": [
    "webmail",
    "teams"
  ],
  "title": "Templates for the whole team"
}
Responses
  • 201 The post.
  • 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.
  • 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/blog/posts/{postId}

A post, with its body rendered

ParameterInType
postId *pathstring
Responses
  • 200 The post and its HTML.
    {
      "html": "\u003cp\u003eTeam admins can now keep \u003cstrong\u003etemplates\u003c/strong\u003e for everyone.\u003c/p\u003e",
      "post": {
        "slug": "shared-templates",
        "status": "draft",
        "title": "Templates for the whole team"
      }
    }
  • 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/blog/posts/{postId}

Change a post

Replaces every field. A draft has no publish time. Needs the content permission.

ParameterInType
postId *pathstring
Request
{
  "body": "...",
  "site": "mailocity",
  "slug": "shared-templates",
  "status": "draft",
  "title": "Templates for the whole team"
}
Responses
  • 200 The post.
  • 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/blog/posts/{postId}

Delete a post

ParameterInType
postId *pathstring
Responses
  • 204 Deleted.
  • 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/blog/preview

Render Markdown as a post shows

Request
{
  "body": "Hello **world**"
}
Responses
  • 200 The HTML.
    {
      "html": "\u003cp\u003eHello \u003cstrong\u003eworld\u003c/strong\u003e\u003c/p\u003e"
    }
  • 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.
  • 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/jobs

The scheduled jobs

The server's recurring work: help desk mail, account sync, billing, deletions, archive retention, history clean-up, unused files, new domains' DNS, automatic DNS and teams' host names. Each with its own interval (defaultEverySeconds), the one in effect (everySeconds), whether it is paused (enabled false), running, its last run (when, how long, its result or error, what started it) and when it runs next. A job runs on one server at a time. See docs/kb/scheduled-jobs.md.

Responses
  • 200 The jobs.
    {
      "items": [
        {
          "defaultEverySeconds": 21600,
          "description": "Deletes message files and attachments nothing uses any more.",
          "enabled": true,
          "everySeconds": 21600,
          "failures": 0,
          "lastDurationMs": 4012,
          "lastError": "",
          "lastFinishedAt": "2026-10-09T12:00:04Z",
          "lastOk": true,
          "lastResult": "12 files and 12 records removed",
          "lastStartedAt": "2026-10-09T12:00:00Z",
          "lastTrigger": "schedule",
          "name": "blob-cleanup",
          "nextAt": "2026-10-09T18:00:00Z",
          "running": false,
          "runs": 40,
          "title": "Unused files"
        }
      ]
    }
  • 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.
PATCH /v1/jobs/{name}

Pause, resume or reschedule a job

A paused job still runs when asked to run now. Needs the settings permission.

ParameterInType
name *pathstring
Request
{
  "everySeconds": 3600
}
Responses
  • 200 The job.
  • 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/jobs/{name}/run

Run a job now

It starts within moments on the server that answers (202); its outcome shows in the list. Where the scheduler is not running (the command line on a server), the job runs at once and the answer (200) gives its outcome. Its next scheduled run counts from this one.

ParameterInType
name *pathstring
Responses
  • 200 Run here; its outcome.
    {
      "durationMs": 41,
      "error": "",
      "finished": true,
      "ok": true,
      "result": "12 mail log lines removed",
      "started": true
    }
  • 202 Started.
  • 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 It is running now.
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, alert and session 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. Sessions end the given minutes after signing in (...SessionMinutes) or after their last use (...IdleMinutes), whichever comes first, for the admin panels, webmail and the Zappocity account; each is 5 to 525600, and an idle limit cannot be longer than its session (422). Shorter limits end sessions already open within 30 seconds; longer ones apply from the next sign-in. Defaults: admin 720 and 60, webmail 10080 and 1440, account 43200 and 10080.

Request
{
  "adminIdleMinutes": 240,
  "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}/dns-automation

The domain's automatic DNS

Whether the domain's DNS records are published at its DNS provider (Route53), in which zone, with which access key (never the secret), and the last publish. See docs/kb/automatic-dns.md.

ParameterInType
domain *pathstring
Responses
  • 200 The automation, or null.
    {
      "automation": {
        "accessKeyId": "AKIAIOSFODNN7EXAMPLE",
        "domain": "acme.com",
        "includeOptional": true,
        "lastChanges": 6,
        "lastError": "",
        "lastSyncAt": "2026-10-09T14:00:00Z",
        "provider": "route53",
        "region": "us-east-1",
        "secretSet": true,
        "zone": "acme.com",
        "zoneId": ""
      }
    }
  • 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}/dns-automation

Publish the domain's records automatically

The records of the domain's DNS list are published in the zone (the domain or one above it) and kept up to date hourly: MX, CNAME and SRV record sets become ours alone; among TXT values, ours replace their own kind (SPF, DMARC, the ownership record, DKIM keys at their selectors) and others at the same name stay. The secret is sealed with the server key and never shown; left out, the stored one is kept for the same access key. The setting is removed when the domain moves to another team.

ParameterInType
domain *pathstring
Request
{
  "useServerCredentials": true
}
Responses
  • 200 The automation.
  • 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}/dns-automation

Stop publishing (records already published stay)

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).
POST /v1/shared-domains/{domain}/dns-automation/sync

Publish the records now

ParameterInType
domain *pathstring
Responses
  • 200 Whether it worked, and the record sets changed (none when all were right).
    {
      "changes": [
        {
          "name": "@",
          "type": "MX",
          "values": [
            "10 mx.mailo.city."
          ]
        }
      ],
      "ok": true
    }
  • 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/signup-verification

Signup verification settings

Whether a signup without an invitation first proves an email address at another provider or a mobile number (mode off, email, sms or either) with a 6-digit code (POST /api/signup/code in api/signup.yaml), and the Twilio account texts are sent through. The auth token is never returned, only whether one is set. See docs/kb/signup-verification.md.

Responses
  • 200 The settings.
    {
      "mode": "either",
      "phoneLimit": 2,
      "smsPrefixes": [
        "+1",
        "+44"
      ],
      "twilioAccountSid": "AC0123456789abcdef0123456789abcdef",
      "twilioAuthTokenSet": true,
      "twilioFrom": "+14155550100",
      "updatedAt": "2026-10-09T14:00:00Z"
    }
  • 401 The platform API key is missing or wrong, or there is no valid admin panel session.
PATCH /v1/signup-verification

Change signup verification

Fields left out stay. twilioAuthToken is sealed with the server key; "" removes it. twilioFrom is a number with its country code or a messaging service SID (MG...). smsPrefixes limits texts to those calling codes (empty: any). phoneLimit is how many accounts one number may verify in 30 days. Email codes come from the notice address (settings noticeFrom).

Request
{
  "mode": "either",
  "twilioAccountSid": "AC0123456789abcdef0123456789abcdef",
  "twilioAuthToken": "...",
  "twilioFrom": "+14155550100"
}
Responses
  • 200 The 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/signup-verification/test

Send a test code by email or text

Request
{
  "method": "sms",
  "to": "+14155550100"
}
Responses
  • 200 Whether it was sent, and why not.
    {
      "error": "sms: Twilio answered 401: Authenticate (20003)",
      "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/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 are open (only while single sign-on is off or not set up). The client secret is never returned.

Responses
  • 200 The settings.
    {
      "adminPasswordSignin": false,
      "clientId": "mailocity",
      "enabled": true,
      "hasClientSecret": true,
      "issuer": "https://zappo.city",
      "webmailPasswordSignin": false
    }
  • 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

While single sign-on is on and set up, passwords sign in nowhere: webmail, the team manager and the admin panel sign in through Zappocity only, mail apps (IMAP, SMTP, ManageSieve, CalDAV/CardDAV, JMAP with Basic) with app passwords only, and webmail refuses mailbox password changes, resets and two-step sign-in. Turning it off (the command line on the server always can) opens the passwords again. webmailPasswordSignin and adminPasswordSignin follow it and cannot be set apart from it (422).

Request
{
  "enabled": true
}
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

Staff are Zappocity accounts in the staff organization (GET/PUT /v1/staff-organization): email must be a confirmed, active Zappocity account that is a member of it, and they sign in with that account. A password is optional, for signing in to the panel directly while that is allowed. Staff who leave the organization are disabled by the account sync within a minute (disabledReason organization) and enabled again if they come back.

Request
{
  "email": "help@example.com",
  "name": "Helper",
  "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 Already staff; or no staff organization is chosen, or email is not a confirmed, active Zappocity account in it (the detail says which).
  • 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/staff-organization

The staff organization

The Zappocity organization staff must belong to; orgId is null until it is chosen.

Responses
  • 200 The organization's id.
    {
      "orgId": "1a2b3c4d-5e6f-4708-9a1b-2c3d4e5f6a7b"
    }
  • 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.
PUT /v1/staff-organization

Choose the staff organization

Staff who are not Zappocity accounts in it are disabled by the account sync within a minute (unless that would leave nobody able to manage staff); the answer lists them. A staff member choosing it must be in it themselves. Needs the staff permission.

Request
{
  "orgId": "1a2b3c4d-5e6f-4708-9a1b-2c3d4e5f6a7b"
}
Responses
  • 200 Chosen, with the staff to be disabled.
    {
      "orgId": "1a2b3c4d-5e6f-4708-9a1b-2c3d4e5f6a7b",
      "toBeDisabled": [
        "old.helper@example.com"
      ]
    }
  • 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 organization.
  • 409 The staff member choosing it is not in it.
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/storage

Where message files are kept

The server's disk (CORPSUITE_BLOB_DIR), or an S3-compatible bucket (CORPSUITE_S3_*), in which case the disk is still read for files not copied there yet. Set in the server's environment, not here; keys are never shown. See docs/kb/message-storage.md.

Responses
  • 200 The storage.
    {
      "backend": "s3",
      "bucket": "acme-mail",
      "dir": "/var/lib/corpsuite/blobs",
      "endpoint": "https://s3.eu-west-1.amazonaws.com",
      "prefix": "corp",
      "region": "eu-west-1"
    }
  • 401 The platform API key is missing or wrong, or there is no valid admin panel session.
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.