{"openapi":"3.1.1","info":{"title":"The Ledger Mail API","version":"2.0.0","summary":"Mail, aliases, custom domains and credentials for a Ledger Mail account.","description":"Three credentials reach `/api/v1/*`, all of them as `Authorization: Bearer …`:\n\n- the **web session's JWT** from `POST /api/v1/auth/login`, which is the account itself and reaches everything its owner may reach;\n- a **personal API token** (`lmk_…`) from `POST /api/v1/tokens`, which reaches only what its scopes name;\n- a **Stalwart OAuth access token**, the credential the MCP endpoint at `/mcp` takes, which reaches the read and mail routes so that an agent that has already done the OAuth dance need not do a second one.\n\nManaging the account's own credentials — creating and revoking tokens and app passwords, listing them, changing the password, 2FA, claiming and verifying a domain — is the web session's alone. No scope covers any of it, so a token can never widen what it may do.\n\nEvery failure answers `{ \"error\": \"…\" }` with a string meant to be read by a person. Limited routes carry `X-RateLimit-Limit`, `X-RateLimit-Remaining` and `X-RateLimit-Reset`, and a 429 carries `Retry-After`. Each credential has limits of its own: one busy token cannot spend another credential's budget.","contact":{"name":"The Ledger Mail","url":"https://www.theledgermail.com"}},"servers":[{"url":"https://api.theledgermail.com","description":"Production"}],"tags":[{"name":"Service","description":"Health, readiness and this document."},{"name":"Authentication","description":"Signing in, refreshing and registering."},{"name":"Mail","description":"Folders, messages, attachments, search and sending."},{"name":"Aliases","description":"Addresses that deliver to this mailbox."},{"name":"Profile","description":"The account holder's details and password."},{"name":"Credentials","description":"Web sessions, mail-client app passwords and API tokens."},{"name":"Domains","description":"Bringing your own domain."},{"name":"Teams","description":"Sharing a mailbox: who is in it and what each of them may do."}],"x-api-token-scopes":{"mail:read":"List folders, list and read messages, download attachments, search","mail:send":"Send mail from the account's own address or one of its active aliases","mail:write":"Flag, move and delete messages, one at a time or in bulk","aliases:read":"List the account's aliases, the domains it may use and whether a name is free","aliases:write":"Create, enable, disable and delete aliases","profile:read":"Read the account's profile"},"paths":{"/api/health":{"get":{"security":[],"tags":["Service"],"summary":"Liveness","description":"Answers as long as the process is up. Takes no credential.","responses":{"200":{"description":"The API is up.","content":{"application/json":{"schema":{"type":"object","required":["status","timestamp"],"properties":{"status":{"type":"string","const":"ok"},"timestamp":{"type":"string","format":"date-time"}}}}}}}}},"/api/readiness":{"get":{"security":[],"tags":["Service"],"summary":"Readiness","description":"Whether the database and the mail server can both be reached.","responses":{"200":{"description":"Ready to serve.","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"string"},"database":{"type":"string"}}}}}},"503":{"description":"The database or the mail server is unavailable.","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"string"},"database":{"type":"string"},"mail":{"type":"string"}}}}}}}}},"/api/openapi.json":{"get":{"security":[],"tags":["Service"],"summary":"This document","responses":{"200":{"description":"The OpenAPI 3.1 description of this API.","content":{"application/json":{"schema":{"type":"object"}}}}}}},"/api/v1/auth/login":{"post":{"security":[],"tags":["Authentication"],"summary":"Sign in","description":"Checks the password against the mail server and returns a pair of JWTs. A browser session is what this is for; a script should use a personal API token instead (POST /api/v1/tokens).","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["email","password"],"properties":{"email":{"type":"string","format":"email"},"password":{"type":"string"}}}}}},"responses":{"200":{"description":"Signed in.","headers":{"X-RateLimit-Limit":{"description":"Requests allowed in the current window for this credential.","schema":{"type":"integer"}},"X-RateLimit-Remaining":{"description":"Requests left in the current window.","schema":{"type":"integer"}},"X-RateLimit-Reset":{"description":"Unix time (seconds) at which the window resets.","schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AuthTokens"}}}},"400":{"description":"The body is not valid JSON, or not an address and a password.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Invalid credentials.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Too many requests. `Retry-After` says how long to wait.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"description":"Password checks are temporarily unavailable.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/auth/refresh":{"post":{"security":[],"tags":["Authentication"],"summary":"Exchange a refresh token","description":"Returns a new pair and invalidates the one presented.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["refreshToken"],"properties":{"refreshToken":{"type":"string"}}}}}},"responses":{"200":{"description":"A new token pair.","headers":{"X-RateLimit-Limit":{"description":"Requests allowed in the current window for this credential.","schema":{"type":"integer"}},"X-RateLimit-Remaining":{"description":"Requests left in the current window.","schema":{"type":"integer"}},"X-RateLimit-Reset":{"description":"Unix time (seconds) at which the window resets.","schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AuthTokens"}}}},"400":{"description":"No refresh token in the body.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"The refresh token is invalid or expired.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Too many requests. `Retry-After` says how long to wait.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/auth/register":{"post":{"security":[],"tags":["Authentication"],"summary":"Create an account","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["local_part","domain","password","first_name","last_name","dob"],"properties":{"local_part":{"type":"string","minLength":2,"maxLength":64},"domain":{"type":"string"},"password":{"type":"string","minLength":8},"first_name":{"type":"string","maxLength":64},"last_name":{"type":"string","maxLength":64},"dob":{"type":"string","description":"YYYY-MM-DD. The account holder must be at least 13."}}}}}},"responses":{"201":{"description":"The account exists and is signed in.","headers":{"X-RateLimit-Limit":{"description":"Requests allowed in the current window for this credential.","schema":{"type":"integer"}},"X-RateLimit-Remaining":{"description":"Requests left in the current window.","schema":{"type":"integer"}},"X-RateLimit-Reset":{"description":"Unix time (seconds) at which the window resets.","schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AuthTokens"}}}},"400":{"description":"The input is not valid, or the mail server refused the password as too weak.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Registration is disabled.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"That address is already taken.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Too many requests. `Retry-After` says how long to wait.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"The mailbox could not be created.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/auth/logout":{"post":{"security":[{"sessionJwt":[]}],"tags":["Authentication"],"summary":"Sign out","description":"Invalidates the refresh token and ends the mail session on every device.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["refreshToken"],"properties":{"refreshToken":{"type":"string"}}}}}},"responses":{"200":{"description":"Signed out.","headers":{"X-RateLimit-Limit":{"description":"Requests allowed in the current window for this credential.","schema":{"type":"integer"}},"X-RateLimit-Remaining":{"description":"Requests left in the current window.","schema":{"type":"integer"}},"X-RateLimit-Reset":{"description":"Unix time (seconds) at which the window resets.","schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Success"}}}},"400":{"description":"No refresh token in the body.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"No credential, or one that is not valid.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Only a signed-in session can sign out.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Too many requests. `Retry-After` says how long to wait.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/auth/username-available":{"get":{"security":[],"tags":["Authentication"],"summary":"Whether a username can be registered","description":"Signing up means choosing a username, and that username becomes the address: `sarah` gives `sarah@theledgermail.com`. Public, because it has to work before anybody has an account. It sees accounts, not addresses held as somebody else's alias or a mailing list — registration is the authority and answers 409 for those.","parameters":[{"name":"username","in":"query","required":true,"description":"The username to check. Folded to lowercase, and held to the same rules registration applies.","schema":{"type":"string","minLength":2,"maxLength":64}}],"responses":{"200":{"description":"Whether it can be registered. `reason` says why not.","headers":{"X-RateLimit-Limit":{"description":"Requests allowed in the current window for this credential.","schema":{"type":"integer"}},"X-RateLimit-Remaining":{"description":"Requests left in the current window.","schema":{"type":"integer"}},"X-RateLimit-Reset":{"description":"Unix time (seconds) at which the window resets.","schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"type":"object","required":["available"],"properties":{"available":{"type":"boolean"},"address":{"type":"string","format":"email","description":"The address this username would give. Present when available."},"reason":{"type":"string","description":"Why it cannot be used. Present when not available."}}}}}},"429":{"description":"Too many requests. `Retry-After` says how long to wait.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/auth/domains":{"get":{"security":[],"tags":["Authentication"],"summary":"Domains open for registration","responses":{"200":{"description":"The domains an account can be created on.","headers":{"X-RateLimit-Limit":{"description":"Requests allowed in the current window for this credential.","schema":{"type":"integer"}},"X-RateLimit-Remaining":{"description":"Requests left in the current window.","schema":{"type":"integer"}},"X-RateLimit-Reset":{"description":"Unix time (seconds) at which the window resets.","schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"type":"object","required":["domains"],"properties":{"domains":{"type":"array","items":{"type":"object","properties":{"domain":{"type":"string"}}}}}}}}},"429":{"description":"Too many requests. `Retry-After` says how long to wait.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"The mail server could not be asked.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/mail/folders":{"get":{"security":[{"sessionJwt":[]},{"apiToken":[]},{"stalwartOAuth":[]}],"x-required-scope":"mail:read","tags":["Mail"],"summary":"List folders","responses":{"200":{"description":"The mailbox's folders.","headers":{"X-RateLimit-Limit":{"description":"Requests allowed in the current window for this credential.","schema":{"type":"integer"}},"X-RateLimit-Remaining":{"description":"Requests left in the current window.","schema":{"type":"integer"}},"X-RateLimit-Reset":{"description":"Unix time (seconds) at which the window resets.","schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"type":"object","required":["folders"],"properties":{"folders":{"type":"array","items":{"$ref":"#/components/schemas/MailFolder"}}}}}}},"401":{"description":"The credential no longer opens the mailbox; sign in again or create a new token.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Missing the \"mail:read\" scope.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Too many requests. `Retry-After` says how long to wait.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"description":"The mail server is initializing.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/mail/folders/{folder}/messages":{"get":{"security":[{"sessionJwt":[]},{"apiToken":[]},{"stalwartOAuth":[]}],"x-required-scope":"mail:read","tags":["Mail"],"summary":"List a folder's messages, newest first","parameters":[{"name":"folder","in":"path","required":true,"description":"Folder path, URL-encoded.","schema":{"type":"string"}},{"name":"page","in":"query","schema":{"type":"integer","minimum":1,"default":1}},{"name":"limit","in":"query","schema":{"type":"integer","minimum":1,"maximum":100,"default":50}}],"responses":{"200":{"description":"One page of the folder.","headers":{"X-RateLimit-Limit":{"description":"Requests allowed in the current window for this credential.","schema":{"type":"integer"}},"X-RateLimit-Remaining":{"description":"Requests left in the current window.","schema":{"type":"integer"}},"X-RateLimit-Reset":{"description":"Unix time (seconds) at which the window resets.","schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/MessageList"}}}},"401":{"description":"The credential no longer opens the mailbox; sign in again or create a new token.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Missing the \"mail:read\" scope.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Too many requests. `Retry-After` says how long to wait.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"description":"The mail server is initializing.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/mail/search":{"get":{"security":[{"sessionJwt":[]},{"apiToken":[]},{"stalwartOAuth":[]}],"x-required-scope":"mail:read","tags":["Mail"],"summary":"Search a folder","parameters":[{"name":"q","in":"query","required":true,"description":"What to look for.","schema":{"type":"string"}},{"name":"folder","in":"query","schema":{"type":"string","default":"INBOX"}},{"name":"page","in":"query","schema":{"type":"integer","minimum":1,"default":1}},{"name":"limit","in":"query","schema":{"type":"integer","minimum":1,"maximum":100,"default":50}}],"responses":{"200":{"description":"Matching messages.","headers":{"X-RateLimit-Limit":{"description":"Requests allowed in the current window for this credential.","schema":{"type":"integer"}},"X-RateLimit-Remaining":{"description":"Requests left in the current window.","schema":{"type":"integer"}},"X-RateLimit-Reset":{"description":"Unix time (seconds) at which the window resets.","schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SearchResult"}}}},"400":{"description":"No search term, or invalid paging.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"The credential no longer opens the mailbox; sign in again or create a new token.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Missing the \"mail:read\" scope.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Too many requests. `Retry-After` says how long to wait.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"The search failed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"description":"The mail server is initializing.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/mail/messages/{uid}":{"get":{"security":[{"sessionJwt":[]},{"apiToken":[]},{"stalwartOAuth":[]}],"x-required-scope":"mail:read","tags":["Mail"],"summary":"Read one message","parameters":[{"name":"uid","in":"path","required":true,"description":"Numeric message uid, as a listing or a search handed it out.","schema":{"type":"integer","minimum":1}},{"name":"folder","in":"query","required":false,"description":"Folder the message is in. Defaults to INBOX.","schema":{"type":"string","default":"INBOX"}}],"responses":{"200":{"description":"The message, with its bodies and attachment list.","headers":{"X-RateLimit-Limit":{"description":"Requests allowed in the current window for this credential.","schema":{"type":"integer"}},"X-RateLimit-Remaining":{"description":"Requests left in the current window.","schema":{"type":"integer"}},"X-RateLimit-Reset":{"description":"Unix time (seconds) at which the window resets.","schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/MessageDetail"}}}},"400":{"description":"The uid is not a positive integer.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"The credential no longer opens the mailbox; sign in again or create a new token.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Missing the \"mail:read\" scope.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"No message with that uid.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Too many requests. `Retry-After` says how long to wait.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"description":"The mail server is initializing.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"delete":{"security":[{"sessionJwt":[]},{"apiToken":[]},{"stalwartOAuth":[]}],"x-required-scope":"mail:write","tags":["Mail"],"summary":"Delete one message","parameters":[{"name":"uid","in":"path","required":true,"description":"Numeric message uid, as a listing or a search handed it out.","schema":{"type":"integer","minimum":1}},{"name":"folder","in":"query","required":false,"description":"Folder the message is in. Defaults to INBOX.","schema":{"type":"string","default":"INBOX"}}],"responses":{"200":{"description":"Deleted.","headers":{"X-RateLimit-Limit":{"description":"Requests allowed in the current window for this credential.","schema":{"type":"integer"}},"X-RateLimit-Remaining":{"description":"Requests left in the current window.","schema":{"type":"integer"}},"X-RateLimit-Reset":{"description":"Unix time (seconds) at which the window resets.","schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Success"}}}},"400":{"description":"The uid is not a positive integer.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"The credential no longer opens the mailbox; sign in again or create a new token.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Missing the \"mail:write\" scope.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Too many requests. `Retry-After` says how long to wait.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"description":"The mail server is initializing.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/mail/messages/{uid}/attachments/{index}":{"get":{"security":[{"sessionJwt":[]},{"apiToken":[]},{"stalwartOAuth":[]}],"x-required-scope":"mail:read","tags":["Mail"],"summary":"Download an attachment","description":"Always served as an opaque download: the part's own Content-Type is never echoed.","parameters":[{"name":"uid","in":"path","required":true,"description":"Numeric message uid, as a listing or a search handed it out.","schema":{"type":"integer","minimum":1}},{"name":"index","in":"path","required":true,"schema":{"type":"integer","minimum":0}},{"name":"folder","in":"query","required":false,"description":"Folder the message is in. Defaults to INBOX.","schema":{"type":"string","default":"INBOX"}}],"responses":{"200":{"description":"The attachment's bytes.","headers":{"X-RateLimit-Limit":{"description":"Requests allowed in the current window for this credential.","schema":{"type":"integer"}},"X-RateLimit-Remaining":{"description":"Requests left in the current window.","schema":{"type":"integer"}},"X-RateLimit-Reset":{"description":"Unix time (seconds) at which the window resets.","schema":{"type":"integer"}},"Content-Disposition":{"description":"attachment, with the part's filename.","schema":{"type":"string"}}},"content":{"application/octet-stream":{"schema":{"type":"string","format":"binary"}}}},"400":{"description":"The uid or the index is not valid.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"The credential no longer opens the mailbox; sign in again or create a new token.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Missing the \"mail:read\" scope.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"No such message or no such attachment.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Too many requests. `Retry-After` says how long to wait.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"description":"The mail server is initializing.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/mail/send":{"post":{"security":[{"sessionJwt":[]},{"apiToken":[]}],"x-required-scope":"mail:send","tags":["Mail"],"summary":"Send a message","description":"Sends and files the copy in Sent in one step. An OAuth access token reaches this only where the deployment allows the MCP send tool (MCP_ALLOW_SEND); where it does not, this operation does not list that credential at all.","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SendRequest"}}}},"responses":{"200":{"description":"Accepted for delivery.","headers":{"X-RateLimit-Limit":{"description":"Requests allowed in the current window for this credential.","schema":{"type":"integer"}},"X-RateLimit-Remaining":{"description":"Requests left in the current window.","schema":{"type":"integer"}},"X-RateLimit-Reset":{"description":"Unix time (seconds) at which the window resets.","schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SendResult"}}}},"400":{"description":"The message is not valid: bad address, too many recipients, attachments too large.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"The credential no longer opens the mailbox; sign in again or create a new token.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"The From address is not the account's own or an active alias of it, or the credential is missing the \"mail:send\" scope.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"413":{"description":"The body is larger than 25 MB of attachments.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Too many requests. `Retry-After` says how long to wait.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"The sender address could not be verified.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"description":"The mail server is initializing.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/mail/messages/{uid}/flag":{"post":{"security":[{"sessionJwt":[]},{"apiToken":[]},{"stalwartOAuth":[]}],"x-required-scope":"mail:write","tags":["Mail"],"summary":"Add or remove flags on one message","parameters":[{"name":"uid","in":"path","required":true,"description":"Numeric message uid, as a listing or a search handed it out.","schema":{"type":"integer","minimum":1}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/FlagRequest"}}}},"responses":{"200":{"description":"Done.","headers":{"X-RateLimit-Limit":{"description":"Requests allowed in the current window for this credential.","schema":{"type":"integer"}},"X-RateLimit-Remaining":{"description":"Requests left in the current window.","schema":{"type":"integer"}},"X-RateLimit-Reset":{"description":"Unix time (seconds) at which the window resets.","schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Success"}}}},"400":{"description":"The uid or the flag data is not valid.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"The credential no longer opens the mailbox; sign in again or create a new token.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Missing the \"mail:write\" scope.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Too many requests. `Retry-After` says how long to wait.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"description":"The mail server is initializing.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/mail/messages/{uid}/move":{"post":{"security":[{"sessionJwt":[]},{"apiToken":[]},{"stalwartOAuth":[]}],"x-required-scope":"mail:write","tags":["Mail"],"summary":"Move one message","parameters":[{"name":"uid","in":"path","required":true,"description":"Numeric message uid, as a listing or a search handed it out.","schema":{"type":"integer","minimum":1}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/MoveRequest"}}}},"responses":{"200":{"description":"Moved.","headers":{"X-RateLimit-Limit":{"description":"Requests allowed in the current window for this credential.","schema":{"type":"integer"}},"X-RateLimit-Remaining":{"description":"Requests left in the current window.","schema":{"type":"integer"}},"X-RateLimit-Reset":{"description":"Unix time (seconds) at which the window resets.","schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Success"}}}},"400":{"description":"The uid or the destination is not valid.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"The credential no longer opens the mailbox; sign in again or create a new token.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Missing the \"mail:write\" scope.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Too many requests. `Retry-After` says how long to wait.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"description":"The mail server is initializing.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/mail/messages/bulk/flag":{"post":{"security":[{"sessionJwt":[]},{"apiToken":[]},{"stalwartOAuth":[]}],"x-required-scope":"mail:write","tags":["Mail"],"summary":"Flag up to 200 messages in one request","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/BulkFlagRequest"}}}},"responses":{"200":{"description":"Done.","headers":{"X-RateLimit-Limit":{"description":"Requests allowed in the current window for this credential.","schema":{"type":"integer"}},"X-RateLimit-Remaining":{"description":"Requests left in the current window.","schema":{"type":"integer"}},"X-RateLimit-Reset":{"description":"Unix time (seconds) at which the window resets.","schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/BulkResult"}}}},"400":{"description":"No uids, more than 200, or invalid flag data.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"The credential no longer opens the mailbox; sign in again or create a new token.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Missing the \"mail:write\" scope.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Too many requests. `Retry-After` says how long to wait.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"description":"The mail server is initializing.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/mail/messages/bulk/move":{"post":{"security":[{"sessionJwt":[]},{"apiToken":[]},{"stalwartOAuth":[]}],"x-required-scope":"mail:write","tags":["Mail"],"summary":"Move up to 200 messages in one request","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/BulkMoveRequest"}}}},"responses":{"200":{"description":"Moved.","headers":{"X-RateLimit-Limit":{"description":"Requests allowed in the current window for this credential.","schema":{"type":"integer"}},"X-RateLimit-Remaining":{"description":"Requests left in the current window.","schema":{"type":"integer"}},"X-RateLimit-Reset":{"description":"Unix time (seconds) at which the window resets.","schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/BulkResult"}}}},"400":{"description":"No uids, more than 200, or no destination.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"The credential no longer opens the mailbox; sign in again or create a new token.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Missing the \"mail:write\" scope.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Too many requests. `Retry-After` says how long to wait.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"description":"The mail server is initializing.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/mail/messages/bulk/delete":{"post":{"security":[{"sessionJwt":[]},{"apiToken":[]},{"stalwartOAuth":[]}],"x-required-scope":"mail:write","tags":["Mail"],"summary":"Delete up to 200 messages in one request","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/BulkDeleteRequest"}}}},"responses":{"200":{"description":"Deleted.","headers":{"X-RateLimit-Limit":{"description":"Requests allowed in the current window for this credential.","schema":{"type":"integer"}},"X-RateLimit-Remaining":{"description":"Requests left in the current window.","schema":{"type":"integer"}},"X-RateLimit-Reset":{"description":"Unix time (seconds) at which the window resets.","schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/BulkResult"}}}},"400":{"description":"No uids, or more than 200.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"The credential no longer opens the mailbox; sign in again or create a new token.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Missing the \"mail:write\" scope.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Too many requests. `Retry-After` says how long to wait.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"description":"The mail server is initializing.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/aliases":{"get":{"security":[{"sessionJwt":[]},{"apiToken":[]},{"stalwartOAuth":[]}],"x-required-scope":"aliases:read","tags":["Aliases"],"summary":"List the account's aliases","responses":{"200":{"description":"The aliases.","headers":{"X-RateLimit-Limit":{"description":"Requests allowed in the current window for this credential.","schema":{"type":"integer"}},"X-RateLimit-Remaining":{"description":"Requests left in the current window.","schema":{"type":"integer"}},"X-RateLimit-Reset":{"description":"Unix time (seconds) at which the window resets.","schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"type":"object","required":["aliases"],"properties":{"aliases":{"type":"array","items":{"$ref":"#/components/schemas/Alias"}}}}}}},"401":{"description":"No credential, or one that is not valid.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Missing the \"aliases:read\" scope.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Too many requests. `Retry-After` says how long to wait.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"post":{"security":[{"sessionJwt":[]},{"apiToken":[]}],"x-required-scope":"aliases:write","tags":["Aliases"],"summary":"Create an alias","description":"Without a name, a random one is generated on the domain given.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["domain"],"properties":{"name":{"type":"string"},"domain":{"type":"string"}}}}}},"responses":{"201":{"description":"The alias exists.","headers":{"X-RateLimit-Limit":{"description":"Requests allowed in the current window for this credential.","schema":{"type":"integer"}},"X-RateLimit-Remaining":{"description":"Requests left in the current window.","schema":{"type":"integer"}},"X-RateLimit-Reset":{"description":"Unix time (seconds) at which the window resets.","schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"type":"object","required":["alias","id"],"properties":{"alias":{"type":"string","format":"email"},"id":{"type":"integer"}}}}}},"400":{"description":"The name is reserved or malformed, the domain is not available, or the address is taken.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"No credential, or one that is not valid.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Missing the \"aliases:write\" scope.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Too many requests. `Retry-After` says how long to wait.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/aliases/domains":{"get":{"security":[{"sessionJwt":[]},{"apiToken":[]},{"stalwartOAuth":[]}],"x-required-scope":"aliases:read","tags":["Aliases"],"summary":"Domains this account may make aliases on","responses":{"200":{"description":"Platform domains plus the account's own verified ones.","headers":{"X-RateLimit-Limit":{"description":"Requests allowed in the current window for this credential.","schema":{"type":"integer"}},"X-RateLimit-Remaining":{"description":"Requests left in the current window.","schema":{"type":"integer"}},"X-RateLimit-Reset":{"description":"Unix time (seconds) at which the window resets.","schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"type":"object","required":["domains"],"properties":{"domains":{"type":"array","items":{"type":"object","properties":{"domain":{"type":"string"}}}}}}}}},"401":{"description":"No credential, or one that is not valid.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Missing the \"aliases:read\" scope.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Too many requests. `Retry-After` says how long to wait.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"The mail server could not be asked.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/aliases/check":{"get":{"security":[{"sessionJwt":[]},{"apiToken":[]},{"stalwartOAuth":[]}],"x-required-scope":"aliases:read","tags":["Aliases"],"summary":"Whether a name is free on a domain","parameters":[{"name":"name","in":"query","required":true,"schema":{"type":"string"}},{"name":"domain","in":"query","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Whether it can be created, and why not when it cannot.","headers":{"X-RateLimit-Limit":{"description":"Requests allowed in the current window for this credential.","schema":{"type":"integer"}},"X-RateLimit-Remaining":{"description":"Requests left in the current window.","schema":{"type":"integer"}},"X-RateLimit-Reset":{"description":"Unix time (seconds) at which the window resets.","schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"type":"object","required":["available"],"properties":{"available":{"type":"boolean"},"error":{"type":"string"}}}}}},"400":{"description":"name and domain are both required.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"No credential, or one that is not valid.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Missing the \"aliases:read\" scope.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Too many requests. `Retry-After` says how long to wait.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/aliases/{id}":{"patch":{"security":[{"sessionJwt":[]},{"apiToken":[]}],"x-required-scope":"aliases:write","tags":["Aliases"],"summary":"Enable or disable an alias","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"integer"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["active"],"properties":{"active":{"type":"boolean"}}}}}},"responses":{"200":{"description":"Done.","headers":{"X-RateLimit-Limit":{"description":"Requests allowed in the current window for this credential.","schema":{"type":"integer"}},"X-RateLimit-Remaining":{"description":"Requests left in the current window.","schema":{"type":"integer"}},"X-RateLimit-Reset":{"description":"Unix time (seconds) at which the window resets.","schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Success"}}}},"400":{"description":"The id or the body is not valid.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"No credential, or one that is not valid.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Missing the \"aliases:write\" scope.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"No such alias on this account.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Too many requests. `Retry-After` says how long to wait.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"delete":{"security":[{"sessionJwt":[]},{"apiToken":[]}],"x-required-scope":"aliases:write","tags":["Aliases"],"summary":"Delete an alias","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"integer"}}],"responses":{"200":{"description":"Deleted.","headers":{"X-RateLimit-Limit":{"description":"Requests allowed in the current window for this credential.","schema":{"type":"integer"}},"X-RateLimit-Remaining":{"description":"Requests left in the current window.","schema":{"type":"integer"}},"X-RateLimit-Reset":{"description":"Unix time (seconds) at which the window resets.","schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Success"}}}},"400":{"description":"The id is not valid.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"No credential, or one that is not valid.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Missing the \"aliases:write\" scope.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"No such alias on this account.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Too many requests. `Retry-After` says how long to wait.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/profile":{"get":{"security":[{"sessionJwt":[]},{"apiToken":[]}],"x-required-scope":"profile:read","tags":["Profile"],"summary":"Read the profile","responses":{"200":{"description":"The profile.","headers":{"X-RateLimit-Limit":{"description":"Requests allowed in the current window for this credential.","schema":{"type":"integer"}},"X-RateLimit-Remaining":{"description":"Requests left in the current window.","schema":{"type":"integer"}},"X-RateLimit-Reset":{"description":"Unix time (seconds) at which the window resets.","schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Profile"}}}},"401":{"description":"No credential, or one that is not valid.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Missing the \"profile:read\" scope.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"No profile has been saved for this account.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Too many requests. `Retry-After` says how long to wait.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"patch":{"security":[{"sessionJwt":[]}],"tags":["Profile"],"summary":"Update the profile","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"first_name":{"type":"string","maxLength":64},"last_name":{"type":"string","maxLength":64},"dob":{"type":"string","description":"YYYY-MM-DD"}}}}}},"responses":{"200":{"description":"Saved.","headers":{"X-RateLimit-Limit":{"description":"Requests allowed in the current window for this credential.","schema":{"type":"integer"}},"X-RateLimit-Remaining":{"description":"Requests left in the current window.","schema":{"type":"integer"}},"X-RateLimit-Reset":{"description":"Unix time (seconds) at which the window resets.","schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Success"}}}},"400":{"description":"Nothing to update, or the profile does not exist yet and not every field was given.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"No credential, or one that is not valid.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Only a signed-in session can change the profile.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Too many requests. `Retry-After` says how long to wait.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/profile/password":{"post":{"security":[{"sessionJwt":[]}],"tags":["Profile"],"summary":"Change the mailbox password","description":"Rotates the web session's app password with it and revokes every API token of the account, along with the mail credential behind each one. Mail-client app passwords keep working: they are revoked one at a time from the credential list.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["current_password","new_password"],"properties":{"current_password":{"type":"string"},"new_password":{"type":"string","minLength":8}}}}}},"responses":{"200":{"description":"Changed.","headers":{"X-RateLimit-Limit":{"description":"Requests allowed in the current window for this credential.","schema":{"type":"integer"}},"X-RateLimit-Remaining":{"description":"Requests left in the current window.","schema":{"type":"integer"}},"X-RateLimit-Reset":{"description":"Unix time (seconds) at which the window resets.","schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Success"}}}},"400":{"description":"The new password is not acceptable, or is the current one.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"The current password is incorrect.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Only a signed-in session can change the password.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Too many requests. `Retry-After` says how long to wait.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"The mail server would not change it.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"description":"Password checks are temporarily unavailable.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/profile/sessions":{"get":{"security":[{"sessionJwt":[]}],"tags":["Credentials"],"summary":"Every credential on the account","description":"`sessions` is the account's Stalwart app passwords, as this route has always reported them. `credentials` is the same account seen whole: browser sessions, mail-client app passwords and API tokens, each typed.","responses":{"200":{"description":"The account's credentials.","headers":{"X-RateLimit-Limit":{"description":"Requests allowed in the current window for this credential.","schema":{"type":"integer"}},"X-RateLimit-Remaining":{"description":"Requests left in the current window.","schema":{"type":"integer"}},"X-RateLimit-Reset":{"description":"Unix time (seconds) at which the window resets.","schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"type":"object","required":["sessions","credentials"],"properties":{"sessions":{"type":"array","items":{"$ref":"#/components/schemas/Session"}},"credentials":{"type":"array","items":{"$ref":"#/components/schemas/Credential"}}}}}}},"401":{"description":"The credential no longer opens the mailbox; sign in again or create a new token.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Only a signed-in session can list credentials.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Too many requests. `Retry-After` says how long to wait.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"The credentials could not be read.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"description":"The mail server is initializing.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/profile/sessions/{id}":{"delete":{"security":[{"sessionJwt":[]}],"tags":["Credentials"],"summary":"Revoke one app password","description":"Takes the id of an app password. Revoking the one in use ends this session.","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Revoked.","headers":{"X-RateLimit-Limit":{"description":"Requests allowed in the current window for this credential.","schema":{"type":"integer"}},"X-RateLimit-Remaining":{"description":"Requests left in the current window.","schema":{"type":"integer"}},"X-RateLimit-Reset":{"description":"Unix time (seconds) at which the window resets.","schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Success"}}}},"401":{"description":"The credential no longer opens the mailbox; sign in again or create a new token.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Only a signed-in session can revoke a credential.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"No such app password.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Too many requests. `Retry-After` says how long to wait.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"The revocation failed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"description":"The mail server is initializing.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/teams":{"get":{"security":[{"sessionJwt":[]}],"tags":["Teams"],"summary":"Every mailbox this account can act on","description":"Your own, plus any you have been given access to. What the mailbox switcher renders. `rights` is everything you may do somewhere in each one and `mailboxRights` the part of that which reaches the mail; an owner has all of both.","responses":{"200":{"description":"The mailboxes, your own first.","headers":{"X-RateLimit-Limit":{"description":"Requests allowed in the current window for this credential.","schema":{"type":"integer"}},"X-RateLimit-Remaining":{"description":"Requests left in the current window.","schema":{"type":"integer"}},"X-RateLimit-Reset":{"description":"Unix time (seconds) at which the window resets.","schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"type":"object","required":["teams"],"properties":{"teams":{"type":"array","items":{"$ref":"#/components/schemas/TeamSummary"}}}}}}},"401":{"description":"No credential, or one that is not valid.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Too many requests. `Retry-After` says how long to wait.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/teams/{id}/members":{"get":{"security":[{"sessionJwt":[]}],"tags":["Teams"],"summary":"Who is in this mailbox, and what each of them may do","parameters":[{"name":"id","in":"path","required":true,"description":"The mailbox's team id, as `GET /api/v1/teams` reports it.","schema":{"type":"string"}}],"responses":{"200":{"description":"The live members and their grants.","headers":{"X-RateLimit-Limit":{"description":"Requests allowed in the current window for this credential.","schema":{"type":"integer"}},"X-RateLimit-Remaining":{"description":"Requests left in the current window.","schema":{"type":"integer"}},"X-RateLimit-Reset":{"description":"Unix time (seconds) at which the window resets.","schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"type":"object","required":["members","limits","shared"],"properties":{"members":{"type":"array","items":{"$ref":"#/components/schemas/TeamMember"}},"limits":{"type":"object","required":["maxGuests","guests"],"properties":{"maxGuests":{"type":"integer"},"guests":{"type":"integer"}}},"shared":{"type":"boolean","description":"Whether a standing key for this mailbox exists."}}}}}},"401":{"description":"No credential, or one that is not valid.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"You are not in this mailbox, or may not manage its members.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Too many requests. `Retry-After` says how long to wait.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/teams/{id}/invitations":{"post":{"security":[{"sessionJwt":[]}],"tags":["Teams"],"summary":"Add somebody to this mailbox","description":"By username, so no mail leaves the system and the membership is live at once. The first guest is what mints this mailbox's standing key, which is why the account's own password is asked for: from that moment the API holds a credential that opens the mailbox without you. The key is listed among your credentials and revoking it ends guest access.","parameters":[{"name":"id","in":"path","required":true,"description":"The mailbox's team id, as `GET /api/v1/teams` reports it.","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["username","password","resource_kind","rights"],"properties":{"username":{"type":"string","minLength":3,"maxLength":32},"password":{"type":"string","minLength":1,"description":"Your own account password."},"resource_kind":{"type":"string","enum":["mailbox","folder","alias","alias_set"]},"resource_id":{"type":"string","nullable":true,"description":"Null for `mailbox`. A JMAP Mailbox id for `folder`. A lowercased address for `alias`. For `alias_set`, one of `all`, `own` or `domain:<domain>`."},"resource_label":{"type":"string","nullable":true,"maxLength":200},"rights":{"type":"array","items":{"$ref":"#/components/schemas/Right"}}}}}}},"responses":{"201":{"description":"They are in.","headers":{"X-RateLimit-Limit":{"description":"Requests allowed in the current window for this credential.","schema":{"type":"integer"}},"X-RateLimit-Remaining":{"description":"Requests left in the current window.","schema":{"type":"integer"}},"X-RateLimit-Reset":{"description":"Unix time (seconds) at which the window resets.","schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"type":"object","required":["membershipId","grantId","username","rights","shared"],"properties":{"membershipId":{"type":"string"},"grantId":{"type":"string"},"username":{"type":"string"},"rights":{"type":"array","items":{"$ref":"#/components/schemas/Right"}},"shared":{"type":"boolean"}}}}}},"400":{"description":"Malformed body, or a right that does not exist.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"No credential, or the password is incorrect.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"You may not manage members, or may not grant a right you do not hold yourself.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"No account by that username.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"Already a member, or a guest allowance is full.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Too many requests. `Retry-After` says how long to wait.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"description":"The mail server could not be asked, or the mailbox key could not be minted.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/teams/{id}/members/{userId}/grants":{"post":{"security":[{"sessionJwt":[]}],"tags":["Teams"],"summary":"Give a member something more","description":"Grants are the UNION of every live grant, so adding a narrower one never takes anything away. To reduce what somebody may do, revoke the grant that gives it.","parameters":[{"name":"id","in":"path","required":true,"description":"The mailbox's team id, as `GET /api/v1/teams` reports it.","schema":{"type":"string"}},{"name":"userId","in":"path","required":true,"description":"The member's opaque user id, as the members list reports it.","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["resource_kind","rights"],"properties":{"resource_kind":{"type":"string","enum":["mailbox","folder","alias","alias_set"]},"resource_id":{"type":"string","nullable":true},"resource_label":{"type":"string","nullable":true,"maxLength":200},"rights":{"type":"array","items":{"$ref":"#/components/schemas/Right"}}}}}}},"responses":{"201":{"description":"The grant.","headers":{"X-RateLimit-Limit":{"description":"Requests allowed in the current window for this credential.","schema":{"type":"integer"}},"X-RateLimit-Remaining":{"description":"Requests left in the current window.","schema":{"type":"integer"}},"X-RateLimit-Reset":{"description":"Unix time (seconds) at which the window resets.","schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"type":"object","required":["grantId","rights"],"properties":{"grantId":{"type":"string"},"rights":{"type":"array","items":{"$ref":"#/components/schemas/Right"}}}}}}},"400":{"description":"Malformed body, or a right that does not exist.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"No credential, or one that is not valid.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"You may not manage members, or may not grant a right you do not hold yourself.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"That person is not in this mailbox.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Too many requests. `Retry-After` says how long to wait.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/teams/{id}/grants/{grantId}":{"patch":{"security":[{"sessionJwt":[]}],"tags":["Teams"],"summary":"Change what a grant allows","description":"Rights only — what a grant is ON is its identity, so re-scoping is a revoke and a create. This is the only way to NARROW somebody without removing their access: rights are unioned, so adding a grant can only ever widen. Bounded by what the caller holds themselves, exactly as creating one is.","parameters":[{"name":"id","in":"path","required":true,"description":"The mailbox's team id, as `GET /api/v1/teams` reports it.","schema":{"type":"string"}},{"name":"grantId","in":"path","required":true,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["rights"],"properties":{"rights":{"type":"array","items":{"$ref":"#/components/schemas/Right"}}}}}}},"responses":{"200":{"description":"The grant as it now stands.","headers":{"X-RateLimit-Limit":{"description":"Requests allowed in the current window for this credential.","schema":{"type":"integer"}},"X-RateLimit-Remaining":{"description":"Requests left in the current window.","schema":{"type":"integer"}},"X-RateLimit-Reset":{"description":"Unix time (seconds) at which the window resets.","schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"type":"object","required":["grantId","rights"],"properties":{"grantId":{"type":"string"},"rights":{"type":"array","items":{"$ref":"#/components/schemas/Right"}}}}}}},"400":{"description":"No rights given (revoke it instead), or a right that does not exist.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"No credential, or one that is not valid.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"You may not manage members of this mailbox, or you do not hold what you are granting.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"No such grant on this mailbox.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"The owner's own access cannot be changed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Too many requests. `Retry-After` says how long to wait.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"delete":{"security":[{"sessionJwt":[]}],"tags":["Teams"],"summary":"Take a grant back","description":"Immediate: the next request that person makes is checked against what is left.","parameters":[{"name":"id","in":"path","required":true,"description":"The mailbox's team id, as `GET /api/v1/teams` reports it.","schema":{"type":"string"}},{"name":"grantId","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Revoked.","headers":{"X-RateLimit-Limit":{"description":"Requests allowed in the current window for this credential.","schema":{"type":"integer"}},"X-RateLimit-Remaining":{"description":"Requests left in the current window.","schema":{"type":"integer"}},"X-RateLimit-Reset":{"description":"Unix time (seconds) at which the window resets.","schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"type":"object","required":["success"],"properties":{"success":{"type":"boolean"}}}}}},"401":{"description":"No credential, or one that is not valid.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"You may not manage members of this mailbox.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"No such grant on this mailbox.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Too many requests. `Retry-After` says how long to wait.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/teams/{id}/members/{userId}":{"delete":{"security":[{"sessionJwt":[]}],"tags":["Teams"],"summary":"Remove somebody from this mailbox","description":"Immediate, and not a deletion: the membership stays as history and so do the read receipts that person generated, attributed to someone who no longer has access. Removing the last guest destroys the mailbox's standing key.","parameters":[{"name":"id","in":"path","required":true,"description":"The mailbox's team id, as `GET /api/v1/teams` reports it.","schema":{"type":"string"}},{"name":"userId","in":"path","required":true,"description":"The member's opaque user id, as the members list reports it.","schema":{"type":"string"}}],"responses":{"200":{"description":"Removed.","headers":{"X-RateLimit-Limit":{"description":"Requests allowed in the current window for this credential.","schema":{"type":"integer"}},"X-RateLimit-Remaining":{"description":"Requests left in the current window.","schema":{"type":"integer"}},"X-RateLimit-Reset":{"description":"Unix time (seconds) at which the window resets.","schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"type":"object","required":["success","sharingEnded"],"properties":{"success":{"type":"boolean"},"sharingEnded":{"type":"boolean","description":"True when that was the last guest and the key was destroyed."}}}}}},"401":{"description":"No credential, or one that is not valid.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"You may not manage members of this mailbox.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"That person is not in this mailbox.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"The owner cannot be removed from their own mailbox.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Too many requests. `Retry-After` says how long to wait.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/tokens":{"get":{"security":[{"sessionJwt":[]}],"tags":["Credentials"],"summary":"List the account's API tokens","description":"Never includes a secret: a token is identified by its label and the last four characters of it.","responses":{"200":{"description":"The live tokens, newest first.","headers":{"X-RateLimit-Limit":{"description":"Requests allowed in the current window for this credential.","schema":{"type":"integer"}},"X-RateLimit-Remaining":{"description":"Requests left in the current window.","schema":{"type":"integer"}},"X-RateLimit-Reset":{"description":"Unix time (seconds) at which the window resets.","schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"type":"object","required":["tokens"],"properties":{"tokens":{"type":"array","items":{"$ref":"#/components/schemas/ApiToken"}}}}}}},"401":{"description":"No credential, or one that is not valid.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Only a signed-in session can list tokens.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Too many requests. `Retry-After` says how long to wait.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"post":{"security":[{"sessionJwt":[]}],"tags":["Credentials"],"summary":"Create an API token","description":"Mints a mail credential of the token's own and returns the secret once. Store it at once: only its SHA-256 is kept, so it cannot be shown again. The account's own password authorizes this, as it does a password change. At most 20 tokens per account.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["label","password","scopes"],"properties":{"label":{"type":"string","minLength":1,"maxLength":64,"description":"What this token is for."},"password":{"type":"string","minLength":1,"description":"The account's own password, checked against the mail server."},"scopes":{"type":"array","items":{"$ref":"#/components/schemas/Scope"}},"expiresInDays":{"type":"integer","minimum":1,"maximum":365,"description":"Without it the token does not expire."}}}}}},"responses":{"201":{"description":"The token, with its secret.","headers":{"X-RateLimit-Limit":{"description":"Requests allowed in the current window for this credential.","schema":{"type":"integer"}},"X-RateLimit-Remaining":{"description":"Requests left in the current window.","schema":{"type":"integer"}},"X-RateLimit-Reset":{"description":"Unix time (seconds) at which the window resets.","schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiTokenCreated"}}}},"400":{"description":"No label, no password, no scopes, an unknown scope, or an expiry out of range.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"No credential, one that is not valid, a wrong account password, or no live mail session to mint against.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Only a signed-in session can create a token.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"The account already has 20 tokens.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Too many requests. `Retry-After` says how long to wait.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"The mail credential could not be minted.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"description":"The mail server is initializing, or password checks are temporarily unavailable.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/tokens/{id}":{"delete":{"security":[{"sessionJwt":[]}],"tags":["Credentials"],"summary":"Revoke an API token","description":"The token stops authenticating at once and the mail credential behind it is revoked. Neither waits on the other: the token is revoked even when the mail server could not be asked, and asking again retries the credential. No other credential of the account is touched, and revoking a token twice is not an error.","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Revoked. The token authenticates nothing from here, whatever the mail server answered.","headers":{"X-RateLimit-Limit":{"description":"Requests allowed in the current window for this credential.","schema":{"type":"integer"}},"X-RateLimit-Remaining":{"description":"Requests left in the current window.","schema":{"type":"integer"}},"X-RateLimit-Reset":{"description":"Unix time (seconds) at which the window resets.","schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Success"}}}},"401":{"description":"No credential, or one that is not valid.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Only a signed-in session can revoke a token.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"No such token on this account.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Too many requests. `Retry-After` says how long to wait.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/app-passwords":{"post":{"security":[{"sessionJwt":[]}],"tags":["Credentials"],"summary":"Create a mail-client app password","description":"For IMAP, SMTP and JMAP on a phone or a desktop client. Unlike an API token it carries no scopes: it is full mailbox access, which is why the account's own password authorizes it. The secret is returned once.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["label","password"],"properties":{"label":{"type":"string","minLength":1,"maxLength":64,"description":"Which device this is for."},"password":{"type":"string","minLength":1,"description":"The account's own password, checked against the mail server."}}}}}},"responses":{"201":{"description":"The credential, with its secret.","headers":{"X-RateLimit-Limit":{"description":"Requests allowed in the current window for this credential.","schema":{"type":"integer"}},"X-RateLimit-Remaining":{"description":"Requests left in the current window.","schema":{"type":"integer"}},"X-RateLimit-Reset":{"description":"Unix time (seconds) at which the window resets.","schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AppPasswordCreated"}}}},"400":{"description":"No label, no password, or a label longer than 64 characters.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"The account password is incorrect, or: The credential no longer opens the mailbox; sign in again or create a new token.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Only a signed-in session can create an app password.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Too many requests. `Retry-After` says how long to wait.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"The credential could not be minted.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"description":"The mail server is initializing, or password checks are temporarily unavailable.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/app-passwords/{id}":{"delete":{"security":[{"sessionJwt":[]}],"tags":["Credentials"],"summary":"Revoke a mail-client app password","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Revoked.","headers":{"X-RateLimit-Limit":{"description":"Requests allowed in the current window for this credential.","schema":{"type":"integer"}},"X-RateLimit-Remaining":{"description":"Requests left in the current window.","schema":{"type":"integer"}},"X-RateLimit-Reset":{"description":"Unix time (seconds) at which the window resets.","schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Success"}}}},"401":{"description":"The credential no longer opens the mailbox; sign in again or create a new token.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Only a signed-in session can revoke an app password.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"No such app password.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"That credential backs an API token; revoke the token instead.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Too many requests. `Retry-After` says how long to wait.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"The revocation failed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"description":"The mail server is initializing.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/domains":{"get":{"security":[{"sessionJwt":[]}],"tags":["Domains"],"summary":"List the account's custom domains","responses":{"200":{"description":"The domains, with how many the plan allows.","headers":{"X-RateLimit-Limit":{"description":"Requests allowed in the current window for this credential.","schema":{"type":"integer"}},"X-RateLimit-Remaining":{"description":"Requests left in the current window.","schema":{"type":"integer"}},"X-RateLimit-Reset":{"description":"Unix time (seconds) at which the window resets.","schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"type":"object","required":["domains","limit","used"],"properties":{"domains":{"type":"array","items":{"$ref":"#/components/schemas/Domain"}},"limit":{"type":"integer"},"used":{"type":"integer"}}}}}},"401":{"description":"No credential, or one that is not valid.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Only a signed-in session can manage domains.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Too many requests. `Retry-After` says how long to wait.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"post":{"security":[{"sessionJwt":[]}],"tags":["Domains"],"summary":"Claim a domain","description":"Answers with the TXT record to publish, which POST /api/v1/domains/{domain}/verify then checks.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["domain"],"properties":{"domain":{"type":"string"}}}}}},"responses":{"200":{"description":"The claim already existed.","headers":{"X-RateLimit-Limit":{"description":"Requests allowed in the current window for this credential.","schema":{"type":"integer"}},"X-RateLimit-Remaining":{"description":"Requests left in the current window.","schema":{"type":"integer"}},"X-RateLimit-Reset":{"description":"Unix time (seconds) at which the window resets.","schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Domain"}}}},"201":{"description":"Claimed; publish the TXT record next.","headers":{"X-RateLimit-Limit":{"description":"Requests allowed in the current window for this credential.","schema":{"type":"integer"}},"X-RateLimit-Remaining":{"description":"Requests left in the current window.","schema":{"type":"integer"}},"X-RateLimit-Reset":{"description":"Unix time (seconds) at which the window resets.","schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PendingDomain"}}}},"400":{"description":"Not a domain this account can claim.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"No credential, or one that is not valid.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"The plan allows no more domains, or only a signed-in session can manage domains.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"The domain is already on this mail system, or claimed by someone else.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Too many requests. `Retry-After` says how long to wait.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Availability could not be checked.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/domains/{domain}":{"get":{"security":[{"sessionJwt":[]}],"tags":["Domains"],"summary":"One domain, with its DNS records","parameters":[{"name":"domain","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"The domain.","headers":{"X-RateLimit-Limit":{"description":"Requests allowed in the current window for this credential.","schema":{"type":"integer"}},"X-RateLimit-Remaining":{"description":"Requests left in the current window.","schema":{"type":"integer"}},"X-RateLimit-Reset":{"description":"Unix time (seconds) at which the window resets.","schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Domain"}}}},"401":{"description":"No credential, or one that is not valid.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Only a signed-in session can manage domains.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"This account has no such domain.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Too many requests. `Retry-After` says how long to wait.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"delete":{"security":[{"sessionJwt":[]}],"tags":["Domains"],"summary":"Remove a domain","parameters":[{"name":"domain","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Removed.","headers":{"X-RateLimit-Limit":{"description":"Requests allowed in the current window for this credential.","schema":{"type":"integer"}},"X-RateLimit-Remaining":{"description":"Requests left in the current window.","schema":{"type":"integer"}},"X-RateLimit-Reset":{"description":"Unix time (seconds) at which the window resets.","schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Success"}}}},"401":{"description":"No credential, or one that is not valid.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Only a signed-in session can manage domains.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"This account has no such domain.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"Aliases still exist on it.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Too many requests. `Retry-After` says how long to wait.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"The mail server would not remove it.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/domains/{domain}/verify":{"post":{"security":[{"sessionJwt":[]}],"tags":["Domains"],"summary":"Check the ownership TXT record and provision the domain","parameters":[{"name":"domain","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Verified and provisioned.","headers":{"X-RateLimit-Limit":{"description":"Requests allowed in the current window for this credential.","schema":{"type":"integer"}},"X-RateLimit-Remaining":{"description":"Requests left in the current window.","schema":{"type":"integer"}},"X-RateLimit-Reset":{"description":"Unix time (seconds) at which the window resets.","schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ActiveDomain"}}}},"400":{"description":"The TXT record was not found yet.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"No credential, or one that is not valid.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Only a signed-in session can verify a domain.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"This account has no such domain.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Too many requests. `Retry-After` says how long to wait.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Provisioning failed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"description":"DNS could not be read right now.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}}},"components":{"schemas":{"Error":{"type":"object","description":"Every failure has this shape. The string is meant to be shown to a person.","required":["error"],"properties":{"error":{"type":"string"}},"additionalProperties":false},"Success":{"type":"object","required":["success"],"properties":{"success":{"type":"boolean","const":true}}},"Address":{"type":"object","properties":{"name":{"type":"string"},"address":{"type":"string","format":"email"}}},"MailFolder":{"type":"object","required":["path","name","delimiter","messages","unseen"],"properties":{"path":{"type":"string","description":"What every other route calls this folder."},"name":{"type":"string"},"delimiter":{"type":"string"},"messages":{"type":"integer"},"unseen":{"type":"integer"},"specialUse":{"type":"string","description":"\\Inbox, \\Sent, \\Drafts, \\Trash, \\Junk or \\Archive."}}},"MessageSummary":{"type":"object","required":["uid","seq","from","to","subject","date","flags","size"],"properties":{"uid":{"type":"integer","description":"Names the message account-wide and survives a move."},"seq":{"type":"integer","description":"1-based position in the newest-first listing that produced it."},"from":{"type":"array","items":{"$ref":"#/components/schemas/Address"}},"to":{"type":"array","items":{"$ref":"#/components/schemas/Address"}},"subject":{"type":"string"},"date":{"type":"string","format":"date-time"},"flags":{"type":"array","items":{"type":"string"}},"size":{"type":"integer"}}},"ListedMessage":{"allOf":[{"$ref":"#/components/schemas/MessageSummary"},{"type":"object","required":["preview"],"properties":{"preview":{"type":"string","description":"One line of the body, at most 256 characters. The sender's plain text: show it as text, never as HTML."}}}]},"MessageAttachment":{"type":"object","required":["index","filename","contentType","size"],"properties":{"index":{"type":"integer","description":"The handle the download route takes."},"filename":{"type":"string"},"contentType":{"type":"string"},"size":{"type":"integer"},"contentId":{"type":"string","description":"Set for an inline part a cid: URL in the HTML body refers to."}}},"MessageDetail":{"allOf":[{"$ref":"#/components/schemas/MessageSummary"},{"type":"object","required":["cc","replyTo","references","attachments"],"properties":{"cc":{"type":"array","items":{"$ref":"#/components/schemas/Address"}},"replyTo":{"type":"array","items":{"$ref":"#/components/schemas/Address"}},"messageId":{"type":"string"},"inReplyTo":{"type":"string"},"references":{"type":"array","items":{"type":"string"}},"html":{"type":"string"},"text":{"type":"string"},"attachments":{"type":"array","items":{"$ref":"#/components/schemas/MessageAttachment"}},"reads":{"type":"array","items":{"$ref":"#/components/schemas/MessageRead"},"description":"Who has opened this message, oldest first, including you. Recorded only for a signed-in person's own session: an API token is a script and an OAuth bearer is a program acting for somebody, and attributing either one's sweep through the mailbox to a person would make this mean nothing."}}}]},"MessageList":{"type":"object","required":["messages","total"],"properties":{"messages":{"type":"array","items":{"$ref":"#/components/schemas/ListedMessage"}},"total":{"type":"integer"}}},"SearchResult":{"allOf":[{"$ref":"#/components/schemas/MessageList"},{"type":"object","required":["page","limit"],"properties":{"page":{"type":"integer"},"limit":{"type":"integer"}}}]},"SendRequest":{"type":"object","required":["from","to","subject"],"description":"`to`, `cc` and `bcc` take one address, a comma-separated list, or an array; a display-name form (`Alice <a@x.com>`) is accepted. At most 50 recipients and 20 attachments, 25 MB of attachments in total.","properties":{"from":{"type":"string","format":"email","description":"The account's own address or one of its active aliases. A bare address, with no display name."},"to":{"$ref":"#/components/schemas/Recipients"},"cc":{"$ref":"#/components/schemas/Recipients"},"bcc":{"$ref":"#/components/schemas/Recipients"},"subject":{"type":"string","maxLength":998},"text":{"type":"string"},"html":{"type":"string"},"inReplyTo":{"type":"string","maxLength":998},"references":{"type":"string","maxLength":4000},"attachments":{"type":"array","items":{"$ref":"#/components/schemas/SendAttachment"}}},"additionalProperties":false},"Recipients":{"oneOf":[{"type":"string"},{"type":"array","items":{"type":"string"}}]},"SendAttachment":{"type":"object","required":["filename","content"],"properties":{"filename":{"type":"string","minLength":1,"maxLength":400},"contentType":{"type":"string","maxLength":255},"content":{"type":"string","contentEncoding":"base64","description":"Standard base64, no whitespace."}}},"SendResult":{"type":"object","required":["messageId"],"properties":{"messageId":{"type":"string","description":"Angle-bracketed Message-ID of the transmitted message."},"savedTo":{"type":"string","description":"Folder the copy was filed in. Absent when no copy could be filed."}}},"Alias":{"type":"object","required":["id","address","active"],"properties":{"id":{"type":"integer","description":"Derived from the address; stable for as long as the alias exists."},"address":{"type":"string","format":"email"},"active":{"type":"boolean"},"created":{"type":"string"},"modified":{"type":["string","null"]},"createdBy":{"type":["object","null"],"description":"Who made this alias, or null for one made outside a user session (the script API) or before provenance was recorded. In a shared mailbox this is what tells the owner's own addresses from a guest's.","properties":{"username":{"type":"string"},"displayName":{"type":["string","null"]},"isYou":{"type":"boolean"}}}}},"MessageRead":{"type":"object","required":["username","displayName","isYou","firstOpenedAt","lastOpenedAt","openCount"],"properties":{"username":{"type":["string","null"],"description":"Null when the account that made this read is gone: a receipt outlives it."},"displayName":{"type":["string","null"]},"isYou":{"type":"boolean"},"firstOpenedAt":{"type":"string"},"lastOpenedAt":{"type":"string"},"openCount":{"type":"integer"}}},"Profile":{"type":"object","required":["email","first_name","last_name","dob","created_at"],"properties":{"email":{"type":"string","format":"email"},"first_name":{"type":"string"},"last_name":{"type":"string"},"dob":{"type":"string","description":"YYYY-MM-DD"},"created_at":{"type":"string"}}},"Session":{"type":"object","description":"One of the account's Stalwart app passwords, as this route has always reported it.","required":["id","description","createdAt","current"],"properties":{"id":{"type":"string"},"description":{"type":"string"},"createdAt":{"type":"string","format":"date-time"},"expiresAt":{"type":["string","null"],"format":"date-time"},"current":{"type":"boolean"}}},"Credential":{"type":"object","description":"One way into the account. `web_session` is a browser sign-in, `app_password` a mail client's IMAP/SMTP/JMAP credential with full mailbox access and no scopes, `api_token` a scoped credential for the REST API.","required":["id","type","label","createdAt","lastUsedAt","expiresAt","current"],"properties":{"id":{"type":"string","description":"For an api_token, the token id DELETE /api/v1/tokens/{id} takes; otherwise the app password's id."},"type":{"type":"string","enum":["web_session","app_password","api_token"]},"label":{"type":"string"},"createdAt":{"type":["string","null"],"format":"date-time"},"lastUsedAt":{"type":["string","null"],"format":"date-time","description":"Known for API tokens, and then to the minute. Null for anything only the mail server tracks."},"expiresAt":{"type":["string","null"],"format":"date-time"},"current":{"type":"boolean","description":"The credential this request was made with."},"scopes":{"type":"array","items":{"$ref":"#/components/schemas/Scope"}}}},"Scope":{"type":"string","enum":["mail:read","mail:send","mail:write","aliases:read","aliases:write","profile:read"]},"Right":{"type":"string","enum":["list","read","flag","move","delete","send_as","aliases_read","aliases_create","aliases_manage","folders_create","manage_members"],"description":"What a grant lets somebody do with a shared mailbox. `read` implies `list`. `manage_members` is out of reach of every credential but a signed-in session."},"TeamSummary":{"type":"object","required":["id","mailbox","name","role","shared","suspended","rights","mailboxRights","grants"],"properties":{"id":{"type":"string"},"mailbox":{"type":"string","format":"email","description":"The address this mailbox is."},"name":{"type":["string","null"]},"role":{"type":"string","enum":["owner","guest"]},"shared":{"type":"boolean","description":"Whether a standing key for this mailbox exists."},"suspended":{"type":"boolean"},"rights":{"type":"array","items":{"$ref":"#/components/schemas/Right"},"description":"Everything you may do somewhere in this mailbox, whatever each grant is scoped to. A summary for display: it does not say what you may do to any particular thing, and nothing is authorised from it."},"mailboxRights":{"type":"array","items":{"$ref":"#/components/schemas/Right"},"description":"The subset of `rights` that applies to the mailbox as a whole — so, what reaches the mail itself. Empty for a guest who was only given aliases: they hold rights here, but none that open a folder."},"grants":{"type":"array","items":{"$ref":"#/components/schemas/TeamGrantSummary"},"description":"Your own grants on this mailbox, so a screen can say what your access is ON. Never anybody else's — that is what `GET /api/v1/teams/{id}/members` and `manage_members` are for."}}},"TeamGrantSummary":{"type":"object","required":["resourceKind","resourceId","resourceLabel","rights"],"properties":{"resourceKind":{"type":"string","enum":["mailbox","folder","alias","alias_set"]},"resourceId":{"type":["string","null"]},"resourceLabel":{"type":["string","null"],"description":"What the resource was called when the grant was made. Display only."},"rights":{"type":"array","items":{"$ref":"#/components/schemas/Right"}}}},"TeamGrant":{"type":"object","required":["id","resourceKind","resourceId","resourceLabel","rights"],"properties":{"id":{"type":"string"},"resourceKind":{"type":"string","enum":["mailbox","folder","alias","alias_set"]},"resourceId":{"type":["string","null"]},"resourceLabel":{"type":["string","null"],"description":"What the resource was called when the grant was made. Display only."},"rights":{"type":"array","items":{"$ref":"#/components/schemas/Right"}}}},"TeamMember":{"type":"object","required":["userId","username","email","displayName","role","disabled","joinedAt","grants"],"properties":{"userId":{"type":"string"},"username":{"type":"string"},"email":{"type":["string","null"],"format":"email","description":"Null for somebody who has no mailbox of their own."},"displayName":{"type":["string","null"]},"role":{"type":"string","enum":["owner","guest"]},"disabled":{"type":"boolean"},"joinedAt":{"type":"string"},"grants":{"type":"array","items":{"$ref":"#/components/schemas/TeamGrant"}}}},"ApiToken":{"type":"object","required":["id","label","scopes","hint","createdAt","lastUsedAt","expiresAt"],"properties":{"id":{"type":"string"},"label":{"type":"string"},"scopes":{"type":"array","items":{"$ref":"#/components/schemas/Scope"}},"hint":{"type":"string","description":"The secret's last four characters, so two tokens can be told apart."},"createdAt":{"type":["string","null"],"format":"date-time"},"lastUsedAt":{"type":["string","null"],"format":"date-time"},"expiresAt":{"type":["string","null"],"format":"date-time"}}},"ApiTokenCreated":{"allOf":[{"$ref":"#/components/schemas/ApiToken"},{"type":"object","required":["token"],"properties":{"token":{"type":"string","pattern":"^lmk_[0-9A-Za-z]{43}$","description":"The secret. Returned by this call and never again: only its SHA-256 is stored."}}}]},"AppPasswordCreated":{"type":"object","required":["id","type","label","username","password","scopes","access"],"properties":{"id":{"type":"string"},"type":{"type":"string","const":"app_password"},"label":{"type":"string"},"username":{"type":"string","format":"email","description":"The login name for IMAP, SMTP and JMAP."},"password":{"type":"string","description":"The secret. Returned by this call and never again."},"scopes":{"type":"null","description":"An app password has none: it is the whole mailbox."},"access":{"type":"string","description":"Says in words what this credential reaches."}}},"DomainVerification":{"type":"object","required":["type","host","value"],"properties":{"type":{"type":"string","const":"TXT"},"host":{"type":"string"},"value":{"type":"string"}}},"DomainRecord":{"type":"object","required":["kind","type","host","value","ok"],"properties":{"kind":{"type":"string","enum":["MX","SPF","DKIM","DMARC"],"description":"What the record is for. NOT a DNS type: three of these four are published as TXT."},"type":{"type":"string","enum":["MX","TXT"],"description":"The DNS record type — the first thing a provider's form asks for."},"host":{"type":"string"},"value":{"type":"string","description":"What goes in the value/content field. For MX this is the target ALONE; the priority is its own field, because a provider that has a separate Priority box turns a value of \"10 mail.host.\" into a mail host called \"10 mail.host.\". The zone-file form is `priority value`."},"priority":{"type":"integer","description":"MX only."},"ok":{"type":["boolean","null"],"description":"Whether DNS already carries it; null when it could not be checked."}}},"PendingDomain":{"type":"object","required":["domain","status","created_at","verification"],"properties":{"domain":{"type":"string"},"status":{"type":"string","const":"pending_verification"},"created_at":{"type":"string"},"verification":{"$ref":"#/components/schemas/DomainVerification"}}},"ActiveDomain":{"type":"object","required":["domain","status","created_at","verified_at"],"properties":{"domain":{"type":"string"},"status":{"type":"string","enum":["active","suspended"]},"created_at":{"type":"string"},"verified_at":{"type":"string"},"records":{"type":"array","items":{"$ref":"#/components/schemas/DomainRecord"}}}},"Domain":{"oneOf":[{"$ref":"#/components/schemas/PendingDomain"},{"$ref":"#/components/schemas/ActiveDomain"}]},"AuthTokens":{"type":"object","required":["accessToken","refreshToken"],"properties":{"accessToken":{"type":"string","description":"Bearer credential for /api/v1/*. Short lived (15 minutes)."},"refreshToken":{"type":"string","description":"Exchanged at /api/v1/auth/refresh. Valid for 7 days."},"user":{"type":"object","properties":{"email":{"type":"string"},"isAdmin":{"type":"boolean"}}}}},"FlagRequest":{"type":"object","required":["flags","action"],"properties":{"flags":{"type":"array","items":{"type":"string","description":"IMAP-style keyword, e.g. \\Seen or \\Flagged."}},"action":{"type":"string","enum":["add","remove"]},"folder":{"type":"string","default":"INBOX"}}},"MoveRequest":{"type":"object","required":["destination"],"properties":{"destination":{"type":"string","description":"Path of the folder to move into."},"folder":{"type":"string","default":"INBOX"}}},"BulkUids":{"type":"array","items":{"type":"integer","minimum":1},"minItems":1,"maxItems":200},"BulkFlagRequest":{"type":"object","required":["uids","flags","action"],"properties":{"uids":{"$ref":"#/components/schemas/BulkUids"},"flags":{"type":"array","items":{"type":"string"}},"action":{"type":"string","enum":["add","remove"]},"folder":{"type":"string","default":"INBOX"}}},"BulkMoveRequest":{"type":"object","required":["uids","destination"],"properties":{"uids":{"$ref":"#/components/schemas/BulkUids"},"destination":{"type":"string"},"folder":{"type":"string","default":"INBOX"}}},"BulkDeleteRequest":{"type":"object","required":["uids"],"properties":{"uids":{"$ref":"#/components/schemas/BulkUids"},"folder":{"type":"string","default":"INBOX"}}},"BulkResult":{"type":"object","required":["success","count"],"properties":{"success":{"type":"boolean","const":true},"count":{"type":"integer"}}}},"securitySchemes":{"sessionJwt":{"type":"http","scheme":"bearer","bearerFormat":"JWT","description":"The access token from `POST /api/v1/auth/login`, valid for 15 minutes. Carries no scopes: it is the signed-in account."},"apiToken":{"type":"http","scheme":"bearer","bearerFormat":"lmk_<43 base62 characters>","description":"A personal API token from `POST /api/v1/tokens`. Each operation names the one scope it needs in `x-required-scope`; a request without it is refused with 403 and a message naming the scope. Available scopes:\n\n- `mail:read` — List folders, list and read messages, download attachments, search\n- `mail:send` — Send mail from the account's own address or one of its active aliases\n- `mail:write` — Flag, move and delete messages, one at a time or in bulk\n- `aliases:read` — List the account's aliases, the domains it may use and whether a name is free\n- `aliases:write` — Create, enable, disable and delete aliases\n- `profile:read` — Read the account's profile"},"stalwartOAuth":{"type":"oauth2","description":"A Stalwart OAuth 2.1 access token, the same credential `/mcp` takes (PKCE, dynamic client registration). It reaches nothing that manages the account, and not the account holder's profile. What this deployment grants it:\n\n- `mail:read` — List folders, list and read messages, download attachments, search\n- `mail:write` — Flag, move and delete messages, one at a time or in bulk\n- `aliases:read` — List the account's aliases, the domains it may use and whether a name is free","flows":{"authorizationCode":{"authorizationUrl":"https://mail.theledgermail.com/login","tokenUrl":"https://mail.theledgermail.com/auth/token","scopes":{"urn:ietf:params:jmap:core":"JMAP access to the account's own mailbox"}}}}}}}