Docs
API reference
Every REST endpoint and object, generated from the OpenAPI file.
Generated from openapi/openapi.yaml (version 0.1.0) at build time. Base URL: https://api.mueslimail.com. The API also serves the file itself at https://api.mueslimail.com/openapi.yaml.
Every operation answers on /v1, the current version, and on the deprecated /v0 alias with the same bodies and status codes. See Versions and SDKs for the deprecation headers, the policy and the official clients. The paths below show the /v0 prefix used by the file; replace it with /v1 and nothing else changes.
Endpoints
GET/v0/auth/me
Caller summary (kept for older clients; same body as GET /v0/me)
Responses
200OK: Me401Error envelope: Error
GET/v0/me
The authenticated caller, a person's session or an API key
Responses
200OK: Me401Error envelope: Error
GET/v0/auth/providers
Which sign in methods this deployment can complete (public)
Responses
200OK: object
POST/v0/auth/magic-link
Email a single use sign in link (public)
Body
| Name | Type | Notes |
|---|---|---|
email | string (email) | required |
redirect_after | string | Path on the site, for example /console/ |
Responses
202Sent (or accepted when the address has no account: same answer)303A form post is redirected back to the login page with sent=1400Error envelope: Error429Error envelope: Error501Error envelope: Error502Error envelope: Error
POST/v0/auth/password/login
Sign in with an email address and a password (public)
Body
| Name | Type | Notes |
|---|---|---|
email | string (email) | required |
password | string | required |
redirect_after | string |
Responses
200Session, or the second factor step (MFAStep): Session303A form post goes to the login page with the session in the fragment, or with ?error=<code>401Error envelope: Error429Error envelope: Error501Error envelope: Error
POST/v0/auth/password/signup
Create an account with an email address and a password (public)
Body
| Name | Type | Notes |
|---|---|---|
email | string (email) | required |
password | string | required, max length 128 |
redirect_after | string |
Responses
202Confirmation link sent (same answer for an existing account)303A form post is redirected back to the sign up page with sent=confirm400Error envelope: Error429Error envelope: Error501Error envelope: Error502Error envelope: Error
GET/v0/auth/password
Whether the person signed in has a password
Responses
200OK: object403Error envelope: Error
PUT/v0/auth/password
Set or change the password of the person signed in
Body
| Name | Type | Notes |
|---|---|---|
current_password | string | |
new_password | string | required, max length 128 |
Responses
200OK: object400Error envelope: Error401Error envelope: Error403Error envelope: Error
GET/v0/auth/callback
Finish a sign in, with a magic link token or an OAuth code (public)
Parameters
| Name | Type | Notes |
|---|---|---|
token | string | in query, Magic link token |
code | string | in query, OAuth authorization code |
state | string | in query, OAuth state, single use, 10 minutes |
Responses
200Session, or the second factor step (MFAStep) when a factor is still to be proved: Session303A browser goes to the login page with the session in the fragment, or with ?error=<code>400Error envelope: Error401Error envelope: Error403Error envelope: Error502Error envelope: Error
GET/v0/auth/oauth/{provider}/start
Start Google, Microsoft or GitHub sign in with PKCE (public)
Parameters
| Name | Type | Notes |
|---|---|---|
provider | google | microsoft | github | in path, required |
redirect | 1 | in query, Send the browser to the provider instead of returning JSON |
redirect_after | string | in query, Path on the site to return to |
Responses
200OK: OAuthStart302The browser goes to the provider400Error envelope: Error501Error envelope: Error
POST/v0/auth/refresh
Rotate a refresh token into a new session (public)
Body
| Name | Type | Notes |
|---|---|---|
refresh_token | string | required |
Responses
200Session: Session400Error envelope: Error401Error envelope: Error501Error envelope: Error
POST/v0/auth/logout
End a session (public)
Body
| Name | Type | Notes |
|---|---|---|
refresh_token | string |
Responses
204Ended (also when the token was already invalid)400Error envelope: Error501Error envelope: Error
GET/v0/inboxes
List inboxes, newest first
Parameters
| Name | Type | Notes |
|---|---|---|
limit | integer | in query, min 1, max 100, default 20 |
page_token | string | in query |
Responses
200OK: InboxList
POST/v0/inboxes
Create an inbox (repeat client_id to get the same inbox)
Body (CreateInbox)
| Name | Type | Notes |
|---|---|---|
username | string | pattern ^[a-z0-9]([a-z0-9._-]{0,62}[a-z0-9])?$ |
domain | string | |
display_name | string | max length 200 |
client_id | string | pattern ^[A-Za-z0-9._~-]{1,256}$ |
Responses
200Existing inbox for this client_id: Inbox201Created: Inbox403Error envelope: Error409Error envelope: Error
GET/v0/inboxes/{inbox}
Get an inbox
Parameters
| Name | Type | Notes |
|---|---|---|
inbox | string (email) | in path, required |
Responses
200OK: Inbox404Error envelope: Error
PATCH/v0/inboxes/{inbox}
Update the display name, the signature, or the support intake of an inbox
Parameters
| Name | Type | Notes |
|---|---|---|
inbox | string (email) | in path, required |
Body
| Name | Type | Notes |
|---|---|---|
display_name | string | max length 200 |
ticket_intake | boolean | New threads on this inbox become tickets in the support queue. |
signature_html | string | HTML signature of this address. Empty string removes it., max length 20000 |
Responses
200OK: Inbox
DELETE/v0/inboxes/{inbox}
Delete an inbox and all its mail
Parameters
| Name | Type | Notes |
|---|---|---|
inbox | string (email) | in path, required |
Responses
204Deleted404Error envelope: Error
GET/v0/inboxes/{inbox}/threads
List threads of an inbox
Parameters
| Name | Type | Notes |
|---|---|---|
inbox | string (email) | in path, required |
limit | integer | in query, min 1, max 100, default 20 |
page_token | string | in query |
label | string | in query |
include_review | boolean | in query, Also return messages held for security review; needs message_review, default false |
Responses
200OK: ThreadList
GET/v0/threads
List threads across the organization
Parameters
| Name | Type | Notes |
|---|---|---|
limit | integer | in query, min 1, max 100, default 20 |
page_token | string | in query |
label | string | in query |
include_review | boolean | in query, Also return messages held for security review; needs message_review, default false |
Responses
200OK: ThreadList
GET/v0/threads/{thread}
Get a thread with the messages the caller may see, oldest first
Parameters
| Name | Type | Notes |
|---|---|---|
thread | string (uuid) | in path, required |
include_review | boolean | in query, Also return messages held for security review; needs message_review, default false |
Responses
200OK: Thread
GET/v0/inboxes/{inbox}/messages
List message metadata (no bodies)
Parameters
| Name | Type | Notes |
|---|---|---|
inbox | string (email) | in path, required |
limit | integer | in query, min 1, max 100, default 20 |
page_token | string | in query |
label | string | in query |
include_review | boolean | in query, Also return messages held for security review; needs message_review, default false |
Responses
200OK: MessageList
POST/v0/inboxes/{inbox}/messages/send
Send a new message (new thread)
Parameters
| Name | Type | Notes |
|---|---|---|
inbox | string (email) | in path, required |
Idempotency-Key | string | in header, max length 256 |
Body (Send)
| Name | Type | Notes |
|---|---|---|
to | string (email)[] | required, at least 1 item |
cc | string (email)[] | |
bcc | string (email)[] | |
subject | string | |
text | string | |
html | string | |
labels | string[] | |
attachments | Attachment[] |
Responses
200Accepted by the transport, or queued when the install has no transport: SendResult403Error envelope: Error409Error envelope: Error422Error envelope: Error429Error envelope: Error502email_delivery_failed: the transport did not accept the message, with delivery_error_code and delivery_error: Error503Error envelope: Error
GET/v0/inboxes/{inbox}/messages/{message}
Get a message with bodies, extracted_text, authentication results and gatekeeper verdict
Parameters
| Name | Type | Notes |
|---|---|---|
inbox | string (email) | in path, required |
message | string | in path, required, Message UUID or RFC Message-ID (URL encoded) |
include_review | boolean | in query, Also return messages held for security review; needs message_review, default false |
Responses
200OK: Message403Error envelope: Error404Error envelope: Error
PATCH/v0/inboxes/{inbox}/messages/{message}
Add or remove custom labels
Parameters
| Name | Type | Notes |
|---|---|---|
inbox | string (email) | in path, required |
message | string | in path, required, Message UUID or RFC Message-ID (URL encoded) |
Body
| Name | Type | Notes |
|---|---|---|
add_labels | string[] | |
remove_labels | string[] |
Responses
200OK: Message
GET/v0/inboxes/{inbox}/messages/{message}/raw
Raw RFC 822 message
Parameters
| Name | Type | Notes |
|---|---|---|
inbox | string (email) | in path, required |
message | string | in path, required, Message UUID or RFC Message-ID (URL encoded) |
include_review | boolean | in query, Also return messages held for security review; needs message_review, default false |
Responses
200OK: message/rfc822
POST/v0/inboxes/{inbox}/messages/{message}/reply
Reply in the same thread
Parameters
| Name | Type | Notes |
|---|---|---|
inbox | string (email) | in path, required |
message | string | in path, required, Message UUID or RFC Message-ID (URL encoded) |
Idempotency-Key | string | in header, max length 256 |
Body (Reply)
| Name | Type | Notes |
|---|---|---|
text | string | |
html | string | |
reply_all | boolean | |
labels | string[] | |
attachments | Attachment[] |
Responses
200Accepted by the transport, or queued when the install has no transport: SendResult403Error envelope: Error422Error envelope: Error429Error envelope: Error502email_delivery_failed: the transport did not accept the reply, with delivery_error_code and delivery_error: Error503Error envelope: Error
POST/v0/inboxes/{inbox}/messages/{message}/release
Release a message held for review (needs message_release, audited)
Parameters
| Name | Type | Notes |
|---|---|---|
inbox | string (email) | in path, required |
message | string | in path, required, Message UUID or RFC Message-ID (URL encoded) |
Responses
200Released: Message403Error envelope: Error404Error envelope: Error409Error envelope: Error
GET/v0/inboxes/{inbox}/messages/{message}/attachments/{attachment}
Download an attachment
Parameters
| Name | Type | Notes |
|---|---|---|
inbox | string (email) | in path, required |
message | string | in path, required, Message UUID or RFC Message-ID (URL encoded) |
attachment | string (uuid) | in path, required |
include_review | boolean | in query, Also return messages held for security review; needs message_review, default false |
Responses
200Attachment bytes with its content type
GET/v0/inboxes/{inbox}/drafts
List drafts, newest first (needs message_read)
Parameters
| Name | Type | Notes |
|---|---|---|
inbox | string (email) | in path, required |
limit | integer | in query, min 1, max 100, default 20 |
page_token | string | in query |
Responses
200OK: DraftList
POST/v0/inboxes/{inbox}/drafts
Create a draft (needs message_send)
Parameters
| Name | Type | Notes |
|---|---|---|
inbox | string (email) | in path, required |
Body (CreateDraft)
| Name | Type | Notes |
|---|---|---|
to | string (email)[] | |
cc | string (email)[] | |
bcc | string (email)[] | |
subject | string | |
text | string | |
html | string | |
labels | string[] | |
attachments | Attachment[] | |
in_reply_to | string | Message UUID or RFC Message-ID to reply to |
reply_all | boolean | With in_reply_to: copy the other recipients |
send_at | string (date-time) | Send automatically at this time (future, within 365 days) |
Responses
201Created: Draft400Error envelope: Error404Error envelope: Error
GET/v0/inboxes/{inbox}/drafts/{draft}
Get a draft (needs message_read)
Parameters
| Name | Type | Notes |
|---|---|---|
inbox | string (email) | in path, required |
draft | string (uuid) | in path, required |
Responses
200OK: Draft404Error envelope: Error
PATCH/v0/inboxes/{inbox}/drafts/{draft}
Update a draft (needs message_send)
Parameters
| Name | Type | Notes |
|---|---|---|
inbox | string (email) | in path, required |
draft | string (uuid) | in path, required |
Body (UpdateDraft)
| Name | Type | Notes |
|---|---|---|
to | string (email)[] | |
cc | string (email)[] | |
bcc | string (email)[] | |
subject | string | |
text | string | |
html | string | |
labels | string[] | |
attachments | Attachment[] | |
send_at | string,null (date-time) | null cancels a scheduled send |
Responses
200OK: Draft400Error envelope: Error404Error envelope: Error
DELETE/v0/inboxes/{inbox}/drafts/{draft}
Delete a draft (needs message_send)
Parameters
| Name | Type | Notes |
|---|---|---|
inbox | string (email) | in path, required |
draft | string (uuid) | in path, required |
Responses
204Deleted
POST/v0/inboxes/{inbox}/drafts/{draft}/send
Send a draft now and delete it (needs message_send)
Parameters
| Name | Type | Notes |
|---|---|---|
inbox | string (email) | in path, required |
draft | string (uuid) | in path, required |
Idempotency-Key | string | in header, max length 256 |
Responses
200Accepted by the transport, or queued when the install has no transport: SendResult400Error envelope: Error403Error envelope: Error404Error envelope: Error409Error envelope: Error429Error envelope: Error502email_delivery_failed: the transport did not accept the message, with delivery_error_code and delivery_error: Error
GET/v0/events/stream
Live events as Server Sent Events (needs message_read)
Parameters
| Name | Type | Notes |
|---|---|---|
types | string | in query, Comma separated event types; all when absent |
inbox | string (email) | in query, Only events of this inbox |
Last-Event-ID | integer | in header, Resume after this event id, min 0 |
last_event_id | integer | in query, Same as Last-Event-ID, for clients that cannot set headers, min 0 |
Responses
200Event stream: string400Error envelope: Error404Error envelope: Error429Error envelope: Error
GET/v0/domains
List own and shared domains
Responses
200OK
POST/v0/domains
Add a custom domain; returns the DNS records to publish
Body
| Name | Type | Notes |
|---|---|---|
domain | string | required |
Responses
201Created: Domain403Error envelope: Error409Error envelope: Error
GET/v0/domains/{domain}
Get a domain with record status
Parameters
| Name | Type | Notes |
|---|---|---|
domain | string | in path, required |
Responses
200OK: Domain
PATCH/v0/domains/{domain}
Move a domain into a group (or out of one), and nominate its catch-all inbox
Parameters
| Name | Type | Notes |
|---|---|---|
domain | string | in path, required |
Body
| Name | Type | Notes |
|---|---|---|
group | string (uuid) | The group id, or null to leave the group |
catch_all_inbox | string (email) | The inbox that receives unknown local parts of this domain, or null to turn the catch-all off |
Responses
200OK: Domain403Error envelope: Error404Error envelope: Error422Error envelope: Error
DELETE/v0/domains/{domain}
Delete a domain without inboxes
Parameters
| Name | Type | Notes |
|---|---|---|
domain | string | in path, required |
Responses
204Deleted409Error envelope: Error
POST/v0/domains/{domain}/verify
Check DNS now (the scheduler also checks every 5 minutes for 7 days)
Parameters
| Name | Type | Notes |
|---|---|---|
domain | string | in path, required |
Responses
200OK: Domain
GET/v0/api-keys
List active keys (requires api_key_read)
Responses
200OK
POST/v0/api-keys
Create a key with at most the caller's permissions (key shown once)
Body
| Name | Type | Notes |
|---|---|---|
name | string | max length 100 |
permissions | Permission[] | omit to copy the caller's permissions |
expires_at | string (date-time) | |
scope | KeyScopeInput |
Responses
201Created: ApiKey403Error envelope: Error
DELETE/v0/api-keys/{key}
Revoke a key immediately (requires api_key_write, no escalation)
Parameters
| Name | Type | Notes |
|---|---|---|
key | string (uuid) | in path, required |
Responses
204Revoked
GET/v0/audit-logs
Security relevant actions of the organization (requires audit_read)
Parameters
| Name | Type | Notes |
|---|---|---|
limit | integer | in query, min 1, max 200, default 50 |
actor_type | user | agent | operator | system | in query |
actor_key_id | string (uuid) | in query |
Responses
200OK: object400Error envelope: Error403Error envelope: Error
GET/v0/webhooks
List webhooks
Responses
200OK
POST/v0/webhooks
Create a webhook (secret is returned once)
Body
| Name | Type | Notes |
|---|---|---|
url | string (uri) | required |
event_types | EventType[] | required, at least 1 item |
inbox_ids | string (email)[] | Inboxes the caller can see (404 otherwise). A webhook keeps the scope of the caller who created it: events of inboxes outside that scope are never delivered, whatever inbox_ids says, and a scoped webhook receives events without an inbox only when its creator is an owner or admin., at most 10 items |
Responses
201Created: Webhook404Error envelope: Error
DELETE/v0/webhooks/{webhook}
Delete a webhook
Parameters
| Name | Type | Notes |
|---|---|---|
webhook | string (uuid) | in path, required |
Responses
204Deleted
GET/v0/billing
Plan and Stripe subscription state (requires billing_read)
Responses
200OK: Billing403Error envelope: Error501Error envelope: Error
POST/v0/billing/checkout
Start a Stripe Checkout for a paid plan (requires billing_write, audited)
Body (Checkout)
| Name | Type | Notes |
|---|---|---|
plan | builder | growth | team | required |
interval | monthly | yearly | default monthly |
success_url | string (uri) | Defaults to the deployment's MAI_BILLING_RETURN_URL |
cancel_url | string (uri) | Defaults to the deployment's MAI_BILLING_RETURN_URL |
Responses
200OK: BillingSession400Error envelope: Error403Error envelope: Error409Error envelope: Error501Error envelope: Error502Error envelope: Error
POST/v0/billing/portal
Open the Stripe customer portal (requires billing_write, audited)
Body
| Name | Type | Notes |
|---|---|---|
return_url | string (uri) |
Responses
200OK: BillingSession403Error envelope: Error409Error envelope: Error501Error envelope: Error502Error envelope: Error
GET/v0/export
Export the organization's own data (requires data_export, audited)
Responses
200OK: OrgExport403Error envelope: Error
POST/v0/organizations
Create the organization of a signed in person (self service sign up)
Body
| Name | Type | Notes |
|---|---|---|
name | string | required, The organization name, trimmed, max length 80 |
Responses
201Created: NewOrganization400Error envelope: Error401Error envelope: Error403Error envelope: Error409Error envelope: Error429Error envelope: Error
GET/v0/organization
The organization and the state of its outbound mail
Responses
200OK: Organization401Error envelope: Error403Error envelope: Error
PATCH/v0/organization
Change the second factor policy or the retention windows (session or key holding member_write)
Body
| Name | Type | Notes |
|---|---|---|
require_mfa | boolean | |
retention | RetentionPolicy |
Responses
200OK: object400Error envelope: Error403Error envelope: Error404Error envelope: Error422Error envelope: Error
DELETE/v0/organization
Delete the organization and all its data (requires data_delete, audited)
Body
| Name | Type | Notes |
|---|---|---|
confirm | string | required, The organization name, verbatim |
Responses
200Deleted; body carries the export taken before deletion: object400Error envelope: Error403Error envelope: Error
GET/v0/tickets
List tickets, newest activity first
Parameters
| Name | Type | Notes |
|---|---|---|
limit | integer | in query, min 1, max 100, default 20 |
page_token | string | in query |
state | TicketState | in query |
priority | TicketPriority | in query |
assignee | string | in query, user id, API key id, or none |
inbox | string | in query, inbox address |
q | string | in query, case insensitive over subject and requester |
Responses
200OK: TicketList
POST/v0/tickets
Open a ticket on an existing thread (requires ticket_write, audited)
Body (CreateTicket)
| Name | Type | Notes |
|---|---|---|
thread_id | string | required, The conversation to open the ticket on. A thread whose previous ticket is resolved or closed accepts a new ticket, with a new number. |
priority | TicketPriority | |
tags | string[] | |
assignee_user | string | |
assignee_key | string |
Responses
201Created: Ticket400Error envelope: Error403Error envelope: Error404Error envelope: Error409Error envelope: Error
GET/v0/tickets/stats
Queue counters and median first reply
Responses
200OK: TicketStats
GET/v0/tickets/{ticket}
Read one ticket with its timeline and internal notes
Parameters
| Name | Type | Notes |
|---|---|---|
ticket | string | in path, required |
Responses
200OK: TicketDetail404Error envelope: Error
PATCH/v0/tickets/{ticket}
Change state, priority, tags or assignment (requires ticket_write, audited)
Parameters
| Name | Type | Notes |
|---|---|---|
ticket | string | in path, required |
Body (UpdateTicket)
| Name | Type | Notes |
|---|---|---|
state | TicketState | |
priority | TicketPriority | |
tags | string[] | |
assignee_user | string | |
assignee_key | string |
Responses
200OK: Ticket400Error envelope: Error404Error envelope: Error409Error envelope: Error
POST/v0/tickets/{ticket}/notes
Add an internal note (requires ticket_write, audited)
Parameters
| Name | Type | Notes |
|---|---|---|
ticket | string | in path, required |
Body
| Name | Type | Notes |
|---|---|---|
body | string | required |
Responses
201Created: TicketNote400Error envelope: Error404Error envelope: Error
POST/v0/tickets/{ticket}/reply
Reply to the customer on the ticket thread (requires message_send, audited)
Parameters
| Name | Type | Notes |
|---|---|---|
ticket | string | in path, required |
Body (Reply)
| Name | Type | Notes |
|---|---|---|
text | string | |
html | string | |
reply_all | boolean | |
labels | string[] | |
attachments | Attachment[] |
Responses
200Sent: SendResult403Error envelope: Error404Error envelope: Error409Error envelope: Error
GET/v0/ticket-views
List the saved views of the queue (requires ticket_read)
Responses
200OK: TicketViewList403Error envelope: Error
POST/v0/ticket-views
Save the current queue filter under a name (requires ticket_write, audited)
Body (CreateTicketView)
| Name | Type | Notes |
|---|---|---|
name | string | required, Up to 60 characters, unique among the views the caller can see |
shared | boolean | Save it for the whole organization; an owner or an admin only |
filters | TicketViewFilters | required |
Responses
201Created: TicketView400Error envelope: Error403Error envelope: Error409Error envelope: Error
DELETE/v0/ticket-views/{view}
Delete a saved view (requires ticket_write, audited)
Parameters
| Name | Type | Notes |
|---|---|---|
view | string | in path, required |
Responses
204Deleted403Error envelope: Error404Error envelope: Error
GET/v0/ticket-sla
Read the SLA policy of the organization (requires ticket_read)
Responses
200OK: TicketSLA403Error envelope: Error
PATCH/v0/ticket-sla
Change the SLA policy (owner or admin, audited)
Body (UpdateTicketSLA)
| Name | Type | Notes |
|---|---|---|
windows | TicketSLAWindow[] | |
targets | TicketSLATargets | |
auto_close_days | integer | min 0, max 365 |
Responses
200OK: TicketSLA400Error envelope: Error403Error envelope: Error
GET/v0/push/subscriptions
List the devices the caller registered (requires notification_write)
Responses
200OK: PushSubscriptionList403Error envelope: Error501Error envelope: Error
POST/v0/push/subscriptions
Register a browser for web push (requires notification_write, audited)
Body (PushSubscribe)
| Name | Type | Notes |
|---|---|---|
endpoint | string (uri) | required, The device at its push service. A capability: never logged, never returned |
keys | object | required |
expiration_time | integer | Milliseconds since the epoch, or null |
user_agent | string | The browser that subscribed, for the device label, max length 512 |
Responses
201Created (or updated): PushSubscription400Error envelope: Error403Error envelope: Error409Error envelope: Error422Error envelope: Error429Error envelope: Error501Error envelope: Error
DELETE/v0/push/subscriptions
Drop a device by endpoint (requires notification_write, audited)
Body (PushUnsubscribe)
| Name | Type | Notes |
|---|---|---|
endpoint | string (uri) | required, The endpoint the browser is dropping |
Responses
204No content, the device is off400Error envelope: Error403Error envelope: Error501Error envelope: Error
DELETE/v0/push/subscriptions/{subscription}
Drop a device by id (requires notification_write, audited)
Parameters
| Name | Type | Notes |
|---|---|---|
subscription | string (uuid) | in path, required |
Responses
204No content, the device is off403Error envelope: Error404Error envelope: Error501Error envelope: Error
GET/v0/members
List the team (requires member_read)
Responses
200OK: MemberList403Error envelope: Error
POST/v0/members
Invite somebody by email (requires member_write, audited)
Body
| Name | Type | Notes |
|---|---|---|
email | string (email) | required |
role | admin | member | default member |
Responses
201Created; the invitation with its one time token: CreatedInvitation400Error envelope: Error403Error envelope: Error409Error envelope: Error429Error envelope: Error
PATCH/v0/members/{user}
Change a role (requires member_write, audited)
Parameters
| Name | Type | Notes |
|---|---|---|
user | string (uuid) | in path, required |
Body
| Name | Type | Notes |
|---|---|---|
role | admin | member | required |
Responses
200OK: Member400Error envelope: Error403Error envelope: Error404Error envelope: Error409Error envelope: Error
DELETE/v0/members/{user}
Remove somebody (requires member_write, audited)
Parameters
| Name | Type | Notes |
|---|---|---|
user | string (uuid) | in path, required |
Responses
204No content403Error envelope: Error404Error envelope: Error
GET/v0/members/invitations
List pending invitations (requires member_read)
Responses
200OK: InvitationList403Error envelope: Error
POST/v0/members/invitations
Invite somebody by email (alias of POST /v0/members)
Body
| Name | Type | Notes |
|---|---|---|
email | string (email) | required |
role | admin | member | default member |
Responses
201Created: CreatedInvitation400Error envelope: Error403Error envelope: Error429Error envelope: Error
DELETE/v0/members/invitations/{invitation}
Cancel an invitation (requires member_write, audited)
Parameters
| Name | Type | Notes |
|---|---|---|
invitation | string (uuid) | in path, required |
Responses
204No content403Error envelope: Error404Error envelope: Error
GET/v0/invitations/{token}
Preview an invitation (public)
Parameters
| Name | Type | Notes |
|---|---|---|
token | string | in path, required |
Responses
200OK: InvitationPreview404Error envelope: Error410Error envelope: Error
POST/v0/invitations/{token}/accept
Accept an invitation (public)
Parameters
| Name | Type | Notes |
|---|---|---|
token | string | in path, required |
Responses
200Accepted; the session and the role: object404Error envelope: Error409Error envelope: Error410Error envelope: Error
GET/v0/groups
List groups (requires group_read)
Responses
200OK: GroupList403Error envelope: Error
POST/v0/groups
Create a group (requires group_write, audited)
Body
| Name | Type | Notes |
|---|---|---|
name | string | required, max length 60 |
color | string | One of the eight palette tokens; a free form colour is refused |
Responses
201Created: Group400Error envelope: Error403Error envelope: Error409Error envelope: Error
PATCH/v0/groups/{group}
Rename or recolour a group (requires group_write, audited)
Parameters
| Name | Type | Notes |
|---|---|---|
group | string (uuid) | in path, required |
Body
| Name | Type | Notes |
|---|---|---|
name | string | max length 60 |
color | string |
Responses
200OK: Group400Error envelope: Error403Error envelope: Error404Error envelope: Error
DELETE/v0/groups/{group}
Delete a group (requires group_write, audited)
Parameters
| Name | Type | Notes |
|---|---|---|
group | string (uuid) | in path, required |
Responses
204No content403Error envelope: Error404Error envelope: Error
GET/v0/inboxes/{inbox}/grants
Who can see this inbox (requires member_read)
Parameters
| Name | Type | Notes |
|---|---|---|
inbox | string (email) | in path, required |
Responses
200OK: GrantList403Error envelope: Error404Error envelope: Error
PUT/v0/inboxes/{inbox}/grants
Grant somebody access to this inbox (requires member_write, audited)
Parameters
| Name | Type | Notes |
|---|---|---|
inbox | string (email) | in path, required |
Body
| Name | Type | Notes |
|---|---|---|
user_id | string (uuid) | required |
rights | viewer | responder | required |
Responses
200OK: Grant400Error envelope: Error403Error envelope: Error404Error envelope: Error
DELETE/v0/inboxes/{inbox}/grants/{user}
Revoke access to this inbox (requires member_write, audited)
Parameters
| Name | Type | Notes |
|---|---|---|
inbox | string (email) | in path, required |
user | string (uuid) | in path, required |
Responses
204No content403Error envelope: Error404Error envelope: Error
GET/v0/domains/{domain}/grants
Who can see this domain (requires member_read)
Parameters
| Name | Type | Notes |
|---|---|---|
domain | string | in path, required |
Responses
200OK: GrantList403Error envelope: Error404Error envelope: Error
PUT/v0/domains/{domain}/grants
Grant somebody access to this domain (requires member_write, audited)
Parameters
| Name | Type | Notes |
|---|---|---|
domain | string | in path, required |
Body
| Name | Type | Notes |
|---|---|---|
user_id | string (uuid) | required |
rights | viewer | responder | required |
Responses
200OK: Grant400Error envelope: Error403Error envelope: Error404Error envelope: Error
DELETE/v0/domains/{domain}/grants/{user}
Revoke access to this domain (requires member_write, audited)
Parameters
| Name | Type | Notes |
|---|---|---|
domain | string | in path, required |
user | string (uuid) | in path, required |
Responses
204No content403Error envelope: Error404Error envelope: Error
POST/stripe/webhook
Stripe webhook endpoint (public, Stripe-Signature verified)
Parameters
| Name | Type | Notes |
|---|---|---|
Stripe-Signature | string | in header, required |
Responses
200Received; outcome is applied, duplicate, stale or ignored400Error envelope: Error501Error envelope: Error
GET/v0/webhooks/{webhook}/events
Events delivered to this webhook, newest first, each with its last attempt (requires webhook_read)
Parameters
| Name | Type | Notes |
|---|---|---|
webhook | string (uuid) | in path, required |
status | pending | succeeded | failed | in query, Filter on the delivery state |
limit | integer | in query, min 1, max 100, default 20 |
page_token | string | in query |
Responses
200OK: WebhookEventList403Error envelope: Error404Error envelope: Error
GET/v0/webhooks/{webhook}/events/{event}/attempts
Every attempt of one event, oldest first (requires webhook_read)
Parameters
| Name | Type | Notes |
|---|---|---|
webhook | string (uuid) | in path, required |
event | string (uuid) | in path, required |
Responses
200OK: WebhookAttemptList403Error envelope: Error404Error envelope: Error
POST/v0/webhooks/{webhook}/events/{event}/replay
Queue the event again for this webhook, also when it succeeded (requires webhook_write, audited)
Parameters
| Name | Type | Notes |
|---|---|---|
webhook | string (uuid) | in path, required |
event | string (uuid) | in path, required |
Responses
202Queued: WebhookReplay403Error envelope: Error404Error envelope: Error
POST/v0/webhooks/{webhook}/rotate-secret
Issue a new signing secret; the previous one verifies for 24 more hours (requires webhook_write, audited)
Parameters
| Name | Type | Notes |
|---|---|---|
webhook | string (uuid) | in path, required |
Responses
200OK: RotateSecret403Error envelope: Error404Error envelope: Error
GET/v0/auth/mfa
This person's second factor and the policy that applies to it
Responses
200OK: MFAStatus403Error envelope: Error
POST/v0/auth/mfa/enroll
Start a second factor for the caller (secret, otpauth URL, ten recovery codes)
Body
| Name | Type | Notes |
|---|---|---|
challenge_token | string |
Responses
201Created: MFAEnrollment400Error envelope: Error403Error envelope: Error409Error envelope: Error
POST/v0/auth/mfa/verify
Confirm a pending second factor with a code from the authenticator
Body
| Name | Type | Notes |
|---|---|---|
code | string | required, max length 6 |
challenge_token | string |
Responses
200OK: MFAVerify400Error envelope: Error401Error envelope: Error403Error envelope: Error409Error envelope: Error
POST/v0/auth/mfa/disable
Remove the caller's second factor (needs a code)
Body
| Name | Type | Notes |
|---|---|---|
code | string | |
recovery_code | string |
Responses
204No content400Error envelope: Error401Error envelope: Error403Error envelope: Error409Error envelope: Error
POST/v0/auth/mfa/challenge
Second step of a sign in, with a code or a recovery code (public)
Body
| Name | Type | Notes |
|---|---|---|
challenge_token | string | required |
code | string | |
recovery_code | string |
Responses
200Session: Session303A browser goes to the login page with the session in the fragment400Error envelope: Error401Error envelope: Error409Error envelope: Error429Error envelope: Error
GET/v0/inboxes/{inbox}/messages/search
Search the messages of an inbox, full text (requires message_read or message_metadata)
Parameters
| Name | Type | Notes |
|---|---|---|
inbox | string (email) | in path, required |
q | string | in query, Free text with quoted exact phrases, -exclusions and field:value tokens |
from | string[] | in query, Sender: case and accent insensitive substring of the From header, repeatable, ORed |
to | string[] | in query, Recipient (To or Cc): repeatable, ORed |
subject | string[] | in query, Subject substring, repeatable, ORed |
has_attachment | boolean | in query, Only messages that carry an attachment, or only those that do not |
label | string[] | in query, Label, repeatable, ORed (any of them) |
is | unread | read | flagged | in query, read, unread or flagged |
after | string (date-time) | in query, Inclusive lower bound on created_at |
before | string (date-time) | in query, Inclusive upper bound on created_at |
limit | integer | in query, min 1, max 100, default 20 |
cursor | string | in query, next_cursor of the previous page |
include_review | boolean | in query, Also return messages held for security review; needs message_review, default false |
Responses
200OK: SearchResult400Error envelope: Error403Error envelope: Error404Error envelope: Error
POST/v0/inboxes/{inbox}/messages/{message}/forward
Forward a message with its original attachments and an optional comment
Parameters
| Name | Type | Notes |
|---|---|---|
inbox | string (email) | in path, required |
message | string | in path, required, Message UUID or RFC Message-ID (URL encoded) |
Idempotency-Key | string | in header, max length 256 |
Body (Forward)
| Name | Type | Notes |
|---|---|---|
to | string (email)[] | required, at least 1 item |
cc | string (email)[] | |
bcc | string (email)[] | |
comment | string | Optional text above the quoted message, below the signature |
labels | string[] |
Responses
200Accepted by the transport, held by the undo window, or queued when the install has no transport: SendResult400Error envelope: Error403Error envelope: Error404Error envelope: Error429Error envelope: Error502email_delivery_failed: the transport refused the forward, with delivery_error_code and delivery_error: Error
POST/v0/inboxes/{inbox}/messages/{message}/cancel
Cancel a held send inside its undo window (needs message_send)
Parameters
| Name | Type | Notes |
|---|---|---|
inbox | string (email) | in path, required |
message | string | in path, required, Message UUID or RFC Message-ID (URL encoded) |
Responses
200Cancelled: Message403Error envelope: Error404Error envelope: Error409Error envelope: Error
POST/v0/messages/batch
Apply one action to up to 1.000 messages, with one outcome per id (requires message_update, or data_delete for a delete)
Body
| Name | Type | Notes |
|---|---|---|
ids | string[] | required, Message UUIDs or RFC Message-IDs, at most 1.000; a repeated id is answered once per occurrence and applied once, at least 1 item, at most 1000 items |
action | add_labels | remove_labels | archive | mark_read | mark_unread | delete | required |
labels | string[] | Required by add_labels and remove_labels, refused by the other four |
Responses
200One outcome per id, whatever they were: BatchResult400Error envelope: Error403Error envelope: Error422Error envelope: Error
GET/v0/rules
List the rules of the organization (requires rule_read)
Parameters
| Name | Type | Notes |
|---|---|---|
target | inbox | domain | org | in query, Only rules of this kind |
target_id | string (uuid) | in query, Only rules of this inbox or domain |
enabled | boolean | in query |
Responses
200OK: object403Error envelope: Error501Error envelope: Error
POST/v0/rules
Create a rule (requires rule_write, audited)
Body (RuleInput)
| Name | Type | Notes |
|---|---|---|
name | string | required, max length 100 |
target | RuleTarget | |
priority | integer | min 0, max 100000, default 100 |
enabled | boolean | default true |
include_spam | boolean | default false |
conditions | RuleConditions | required |
actions | RuleAction[] | required, at least 1 item, at most 20 items |
Responses
201Created: Rule400Error envelope: Error403Error envelope: Error404Error envelope: Error422Error envelope: Error429Error envelope: Error501Error envelope: Error
GET/v0/rules/{rule}
Read one rule (requires rule_read)
Parameters
| Name | Type | Notes |
|---|---|---|
rule | string (uuid) | in path, required |
Responses
200OK: Rule403Error envelope: Error404Error envelope: Error501Error envelope: Error
PATCH/v0/rules/{rule}
Change a rule, or switch it off (requires rule_write, audited)
Parameters
| Name | Type | Notes |
|---|---|---|
rule | string (uuid) | in path, required |
Body (RulePatch)
| Name | Type | Notes |
|---|---|---|
name | string | max length 100 |
target | RuleTarget | |
priority | integer | min 0, max 100000 |
enabled | boolean | |
include_spam | boolean | |
conditions | RuleConditions | |
actions | RuleAction[] | at least 1 item, at most 20 items |
Responses
200OK: Rule400Error envelope: Error403Error envelope: Error404Error envelope: Error422Error envelope: Error501Error envelope: Error
DELETE/v0/rules/{rule}
Delete a rule and any apply job queued for it (requires rule_write, audited)
Parameters
| Name | Type | Notes |
|---|---|---|
rule | string (uuid) | in path, required |
Responses
204Deleted403Error envelope: Error404Error envelope: Error501Error envelope: Error
GET/v0/inboxes/{inbox}/agent
The inbox agent of one inbox, its actions and its state (requires inbox_read)
Parameters
| Name | Type | Notes |
|---|---|---|
inbox | string (email) | in path, required |
Responses
200OK: AgentConfig403Error envelope: Error404Error envelope: Error501Error envelope: Error
PATCH/v0/inboxes/{inbox}/agent
Configure the inbox agent, replacing the action set and patching the rest (requires inbox_write)
Parameters
| Name | Type | Notes |
|---|---|---|
inbox | string (email) | in path, required |
Body (AgentConfigRequest)
| Name | Type | Notes |
|---|---|---|
instructions | string | 1 to 4000 characters, or null to clear, max length 4000 |
allowed_actions | draft_reply | draft_forward | add_labels | assign_thread | mark_spam | archive_thread | move_to_trash[] | The whole set; an empty array turns the agent off, one of draft_reply, draft_forward, add_labels, assign_thread, mark_spam, archive_thread, move_to_trash |
tone | concise_professional | concise_casual | expanded_professional | expanded_casual | |
enabled | boolean |
Responses
200OK: AgentConfig403Error envelope: Error404Error envelope: Error422Error envelope: Error501Error envelope: Error
POST/v0/rules/{rule}/apply
Apply a rule to the messages that already exist (requires rule_write, audited)
Parameters
| Name | Type | Notes |
|---|---|---|
rule | string (uuid) | in path, required |
Responses
202Queued: RuleApplyReport403Error envelope: Error404Error envelope: Error409Error envelope: Error501Error envelope: Error
GET/v0/rules/applies/{apply}
The state of one apply job (requires rule_read)
Parameters
| Name | Type | Notes |
|---|---|---|
apply | string (uuid) | in path, required |
Responses
200OK: RuleApplyReport403Error envelope: Error404Error envelope: Error501Error envelope: Error
GET/v0/inboxes/{inbox}/auto-reply
The automatic answer of one inbox and whether it is inside its window (requires rule_read)
Parameters
| Name | Type | Notes |
|---|---|---|
inbox | string (email) | in path, required |
Responses
200OK: AutoReply403Error envelope: Error404Error envelope: Error
PUT/v0/inboxes/{inbox}/auto-reply
Set the automatic answer of one inbox (requires rule_write, audited)
Parameters
| Name | Type | Notes |
|---|---|---|
inbox | string (email) | in path, required |
Body (AutoReplyInput)
| Name | Type | Notes |
|---|---|---|
mode | fixed | daily | weekly | monthly | permanent | required |
timezone | string | required, IANA zone, for example Europe/Rome; required, never derived from the server |
start | string | RFC 3339 for fixed, HH:MM for daily, weekly and monthly; absent for permanent |
end | string | RFC 3339 for fixed, HH:MM for daily, weekly and monthly; absent for permanent |
days | string[] | Weekday tokens for weekly, day-of-month numbers for monthly, absent otherwise, at most 31 items |
subject | string | required, max length 256 |
body_text | string | required, max length 4000 |
body_html | string | |
restrict_to_domain | boolean | default false |
Responses
200OK: AutoReply400Error envelope: Error403Error envelope: Error404Error envelope: Error429Error envelope: Error501Error envelope: Error
DELETE/v0/inboxes/{inbox}/auto-reply
Remove the automatic answer of one inbox and the rule that carried it (requires rule_write, audited)
Parameters
| Name | Type | Notes |
|---|---|---|
inbox | string (email) | in path, required |
Responses
204Removed403Error envelope: Error404Error envelope: Error
GET/v0/inboxes/{inbox}/blocklist
The block and allow list of one inbox (requires inbox_read)
Parameters
| Name | Type | Notes |
|---|---|---|
inbox | string (email) | in path, required |
Responses
200OK: object403Error envelope: Error404Error envelope: Error
POST/v0/inboxes/{inbox}/blocklist
Add an entry to the block or allow list of one inbox (requires inbox_write, audited)
Parameters
| Name | Type | Notes |
|---|---|---|
inbox | string (email) | in path, required |
Body (BlocklistInput)
| Name | Type | Notes |
|---|---|---|
list_kind | block | allow | default block |
kind | sender | domain | required |
value | string | required, An address for kind sender, a domain for kind domain; never one as the other, max length 320 |
Responses
201Created: BlocklistEntry400Error envelope: Error403Error envelope: Error404Error envelope: Error429Error envelope: Error
DELETE/v0/inboxes/{inbox}/blocklist
Remove an entry of the list of one inbox, named in the query string (requires inbox_write, audited)
Parameters
| Name | Type | Notes |
|---|---|---|
inbox | string (email) | in path, required |
kind | sender | domain | in query, required |
value | string | in query, required |
list_kind | block | allow | in query, default block |
Responses
204Removed400Error envelope: Error403Error envelope: Error404Error envelope: Error
GET/v0/organization/blocklist
The organization wide block and allow list (requires org_blocklist_write)
Responses
200OK: object403Error envelope: Error
POST/v0/organization/blocklist
Add an entry to the organization wide list (requires org_blocklist_write, audited)
Body (BlocklistInput)
| Name | Type | Notes |
|---|---|---|
list_kind | block | allow | default block |
kind | sender | domain | required |
value | string | required, An address for kind sender, a domain for kind domain; never one as the other, max length 320 |
Responses
201Created: BlocklistEntry400Error envelope: Error403Error envelope: Error429Error envelope: Error
DELETE/v0/organization/blocklist
Remove an entry of the organization wide list (requires org_blocklist_write, audited)
Parameters
| Name | Type | Notes |
|---|---|---|
kind | sender | domain | in query, required |
value | string | in query, required |
list_kind | block | allow | in query, default block |
Responses
204Removed400Error envelope: Error
GET/v0/inboxes/{inbox}/messages/{message}/timeline
The delivery states of one message, oldest first, with the source of each (requires message_read)
Parameters
| Name | Type | Notes |
|---|---|---|
inbox | string (email) | in path, required |
message | string | in path, required, Message UUID or RFC Message-ID (URL encoded) |
Responses
200OK: MessageTimeline403Error envelope: Error404Error envelope: Error
GET/v0/domains/{domain}/reputation
Bounce and complaint reputation of a domain, measured like the sending breaker (requires domain_read)
Parameters
| Name | Type | Notes |
|---|---|---|
domain | string | in path, required |
Responses
200OK: DomainReputation403Error envelope: Error404Error envelope: Error
POST/v0/oauth/token
Exchange an authorization code, or a refresh token, for an access token (public)
Body
| Name | Type | Notes |
|---|---|---|
grant_type | authorization_code | refresh_token | required |
code | string | |
redirect_uri | string (uri) | |
client_id | string | |
code_verifier | string | |
refresh_token | string |
Responses
200OK: OAuthToken400invalid_request, invalid_grant, invalid_client, unsupported_grant_type (RFC 6749 body): object501oauth_disabled: object
POST/v0/oauth/register
Register a client dynamically (public; public clients only, PKCE, rate limited per address)
Body (OAuthClientInput)
| Name | Type | Notes |
|---|---|---|
client_name | string | required, max length 100 |
client_uri | string (uri) | Where a person can read about the app |
redirect_uris | string (uri)[] | required, https, or http on a loopback host; no fragment, at least 1 item, at most 10 items |
scope | messages:send | full_access | The most this client may ever ask for, default messages:send |
Responses
201Created: OAuthClient400Error envelope: Error429Error envelope: Error501Error envelope: Error
GET/v0/oauth/grants
The apps this person connected (session; a key is refused with session_required)
Responses
200OK: OAuthGrantList403Error envelope: Error501Error envelope: Error
POST/v0/oauth/grants/{grant}/revoke
Revoke one grant, immediately on every token it issued (session; audited)
Parameters
| Name | Type | Notes |
|---|---|---|
grant | string (uuid) | in path, required |
Responses
204No content403Error envelope: Error404Error envelope: Error501Error envelope: Error
GET/v0/logs
Redacted API request log of the organization, newest first (requires audit_read)
Parameters
| Name | Type | Notes |
|---|---|---|
limit | integer | in query, min 1, max 200, default 50 |
key_id | string (uuid) | in query |
method | string | in query |
status | integer | in query, min 100, max 599 |
sdk | go | node | python | php | ruby | java | in query |
after | string (date-time) | in query |
before | string (date-time) | in query |
Responses
200OK: object400Error envelope: Error403Error envelope: Error501Error envelope: Error
GET/v0/logs/{log}
One API request with its redacted request and response body (requires audit_read)
Parameters
| Name | Type | Notes |
|---|---|---|
log | string (uuid) | in path, required |
Responses
200OK: RequestLog403Error envelope: Error404Error envelope: Error501Error envelope: Error
GET/v0/logs/{log}/export
The same request as a markdown block a person can paste into an assistant (requires audit_read)
Parameters
| Name | Type | Notes |
|---|---|---|
log | string (uuid) | in path, required |
Responses
200OK: string403Error envelope: Error404Error envelope: Error501Error envelope: Error
GET/v0/suppressions
The suppression list of the organization (requires suppression_write)
Parameters
| Name | Type | Notes |
|---|---|---|
origin | bounce | complaint | manual | in query |
limit | integer | in query, min 1, max 100, default 20 |
page_token | string | in query |
Responses
GET/v0/roles
List custom roles (requires member_read)
Responses
200OK: CustomRoleList403Error envelope: Error
POST/v0/roles
Create a custom role (requires member_write, audited)
Body (CustomRoleRequest)
| Name | Type | Notes |
|---|---|---|
name | string | max length 60 |
description | string | max length 200 |
permissions | Permission[] | Required on create. An unknown name is 422 unknown_permission; a name the caller does not hold is 403 permission_escalation. |
Responses
201Created: CustomRole400Error envelope: Error403Error envelope: Error409Error envelope: Error422Error envelope: Error
GET/v0/suppressions/export
The whole list as CSV (requires suppression_write, audited)
Responses
200OK: string403Error envelope: Error
POST/v0/suppressions/import
Import a CSV of addresses (requires suppression_write, audited)
Responses
201Created: SuppressionResult400Error envelope: Error403Error envelope: Error413Error envelope: Error
GET/v0/unsubscribe/{token}
The unsubscribe page of a one-click link (public)
Parameters
| Name | Type | Notes |
|---|---|---|
token | string | in path, required |
Responses
200OK: string404The link is not valid: string501unsubscribe_disabled: this deployment has no signing secret or no public base URL
POST/v0/unsubscribe/{token}
One-click unsubscribe (public, RFC 8058)
Parameters
| Name | Type | Notes |
|---|---|---|
token | string | in path, required |
Responses
200OK: string404The link is not valid: string501unsubscribe_disabled: this deployment has no signing secret or no public base URL
PATCH/v0/roles/{role}
Change a role's name, description or permission set (requires member_write, audited)
Parameters
| Name | Type | Notes |
|---|---|---|
role | string (uuid) | in path, required |
Body (CustomRoleRequest)
| Name | Type | Notes |
|---|---|---|
name | string | max length 60 |
description | string | max length 200 |
permissions | Permission[] | Required on create. An unknown name is 422 unknown_permission; a name the caller does not hold is 403 permission_escalation. |
Responses
200OK: CustomRole400Error envelope: Error403Error envelope: Error404Error envelope: Error409Error envelope: Error422Error envelope: Error
DELETE/v0/roles/{role}
Delete a custom role (requires member_write, audited)
Parameters
| Name | Type | Notes |
|---|---|---|
role | string (uuid) | in path, required |
Responses
204Deleted, assignments revoked403Error envelope: Error404Error envelope: Error
PUT/v0/members/{user}/roles
Replace the custom roles of a person (requires member_write, audited)
Parameters
| Name | Type | Notes |
|---|---|---|
user | string (uuid) | in path, required |
Body (RoleAssignmentRequest)
| Name | Type | Notes |
|---|---|---|
role_ids | string (uuid)[] | required, The whole set the subject should hold. An empty array revokes every custom role from the subject; that is a revocation, not a mistake., at most 20 items |
Responses
200OK: RoleAssignmentChange400Error envelope: Error403Error envelope: Error404Error envelope: Error
PUT/v0/groups/{group}/roles
Replace the custom roles of a group (requires member_write, audited)
Parameters
| Name | Type | Notes |
|---|---|---|
group | string (uuid) | in path, required |
Body (RoleAssignmentRequest)
| Name | Type | Notes |
|---|---|---|
role_ids | string (uuid)[] | required, The whole set the subject should hold. An empty array revokes every custom role from the subject; that is a revocation, not a mistake., at most 20 items |
Responses
200OK: RoleAssignmentChange400Error envelope: Error403Error envelope: Error404Error envelope: Error
GET/v0/export/{export}
The state of one export job (requires data_export)
Parameters
| Name | Type | Notes |
|---|---|---|
export | string (uuid) | in path, required |
Responses
200OK: ExportJob403Error envelope: Error404Error envelope: Error
GET/v0/export/{export}/download
The document of a finished export job (requires data_export)
Parameters
| Name | Type | Notes |
|---|---|---|
export | string (uuid) | in path, required |
Responses
200OK: OrgExport403Error envelope: Error404Error envelope: Error409Error envelope: Error
GET/v0/organization/features
The feature switches of the caller's organization (read only)
Responses
200OK: object401Error envelope: Error
Objects
User object
| Name | Type | Notes |
|---|---|---|
user_id | string (uuid) | |
email | string (email) | |
name | string | |
provider | magic_link | google | microsoft | github | Identity that first proved this address |
role | owner | admin | member | |
created_at | string (date-time) | |
last_login_at | string (date-time) |
Session object
| Name | Type | Notes |
|---|---|---|
access_token | string | Signed JWT, send as Authorization: Bearer, 15 minutes |
refresh_token | string | Single use, 30 days, rotate it at POST /v0/auth/refresh |
token_type | Bearer | |
expires_in | integer | Seconds until the access token expires |
expires_at | string (date-time) | |
refresh_expires_at | string (date-time) | |
role | owner | admin | member | |
user | User | |
organization | Org |
OAuthStart object
| Name | Type | Notes |
|---|---|---|
provider | google | github | |
authorize_url | string | Provider URL, with state and the S256 PKCE challenge |
state | string | Single use, 10 minutes |
expires_at | string (date-time) |
Me object
| Name | Type | Notes |
|---|---|---|
auth | api_key | session | |
organization_id | string (uuid) | Empty when a session has no organization yet |
name | string | |
plan | free | builder | growth | team | |
screen_before_notify | boolean | |
role | owner | admin | member | Only for a session; null while it has no organization |
needs_organization | boolean | True for a session that signed in and has not named an organization yet; only GET /v0/auth/me and POST /v0/organizations answer until then |
api_key_id | string (uuid) | Only for an API key |
user | object | Only for a session |
organization | Org | |
permissions | string[] | |
expires_at | string (date-time) | |
features | OrganizationFeatures |
Org object
| Name | Type | Notes |
|---|---|---|
organization_id | string (uuid) | |
name | string | |
plan | free | builder | growth | team | |
screen_before_notify | boolean | Hold inbound mail until screened (operator setting: mai admin org-screening) |
Organization object
| Name | Type | Notes |
|---|---|---|
organization_id | string (uuid) | |
name | string | |
plan | free | builder | growth | team | |
screen_before_notify | boolean | |
retention | RetentionPolicy | |
sending | OrgSending |
OrgSending object
| Name | Type | Notes |
|---|---|---|
state | ok | degraded | blocked | degraded: the last three sends in a row were refused by the transport. blocked: every attempt of the last 24 hours was refused for an authorization reason, so nothing can be delivered until the provider allows this server or the credentials are fixed. |
reason_code | unauthorized_ip | auth_failed | relay_unreachable | connection_refused | tls_failed | unknown | The fault code of the newest refused attempt; null while state is ok. |
since | string (date-time) | When the current state began; null while state is ok. |
NewOrganization object
| Name | Type | Notes |
|---|---|---|
organization_id | string (uuid) | |
name | string | |
plan | free | builder | growth | team | Free: self service sign up needs no card |
role | owner | admin | member | The caller's role in the new organization: owner |
CreateInbox object
| Name | Type | Notes |
|---|---|---|
username | string | pattern ^[a-z0-9]([a-z0-9._-]{0,62}[a-z0-9])?$ |
domain | string | |
display_name | string | max length 200 |
client_id | string | pattern ^[A-Za-z0-9._~-]{1,256}$ |
Inbox object
| Name | Type | Notes |
|---|---|---|
id | string (uuid) | |
inbox_id | string (email) | |
email | string (email) | |
display_name | string | |
client_id | string | |
ticket_intake | boolean | New threads on this inbox become tickets. |
sending_paused_at | string (date-time) | Set when the breaker paused sending for this inbox; null when only the organization is paused, which sending_health reports in paused_scope. An operator resumes it. |
sending_paused_reason | string | |
sending_health | SendingHealth | |
signature_html | string | The signature of this address, HTML, applied to every message composed after it was written. Changing it never rewrites a draft that already exists (the rule of Proton): a draft keeps the signature of its own moment. |
group | GroupRef | |
grants_count | integer | How many people hold access directly on this inbox. Null unless the caller is the owner or an admin. |
created_at | string (date-time) | |
updated_at | string (date-time) |
SendingHealth object
| Name | Type | Notes |
|---|---|---|
state | ok | paused | paused means sending from this inbox is refused with 403 sending_paused. |
paused_scope | string | What paused sending: inbox, organization, or empty while state is ok. A send is refused by both scopes, so this is what tells them apart. Not an enum because the empty string is a value, and a validator that rejected it would fail on every healthy inbox. |
paused_at | string (date-time) | When the breaker paused the inbox or its organization; null while state is ok. |
paused_reason | string | The same sentence the 403 sending_paused carries, naming the window and the limit that was crossed, and the counts at the moment of the pause. |
window_hours | integer | Length of the rolling window the counts cover. |
sends | integer | Messages that reached a final outcome (accepted or rejected) in the window. |
hard_bounces | integer | Permanent rejections at send time plus bounces reported later by a delivery status notification. |
complaints | integer | Spam complaints reported through the feedback loop. |
bounce_rate | number | hard_bounces divided by sends, 0 when sends is 0. |
complaint_rate | number | complaints divided by sends, 0 when sends is 0. |
thresholds | SendingThresholds |
InboxList object
| Name | Type | Notes |
|---|---|---|
count | integer | |
next_page_token | string | |
inboxes | Inbox[] |
Thread object
| Name | Type | Notes |
|---|---|---|
thread_id | string (uuid) | |
inbox_id | string | |
subject | string | |
preview | string | |
labels | string[] | |
message_count | integer | |
timestamp | string (date-time) | |
messages | Message[] | |
retention_purged_at | string (date-time) | Set when retention deleted the last message of the thread. A thread with no messages answers 200 with this and retention_notice, instead of 404, because a ticket may point at it. |
retention_notice | string | The sentence in place of a body the retention window removed. |
ThreadList object
| Name | Type | Notes |
|---|---|---|
count | integer | |
next_page_token | string | |
threads | Thread[] |
Message object
| Name | Type | Notes |
|---|---|---|
id | string (uuid) | |
message_id | string | RFC 5322 Message-ID with angle brackets |
thread_id | string (uuid) | |
inbox_id | string | |
direction | in | out | |
from | string | |
to | string[] | |
cc | string[] | |
subject | string | |
text | string | |
html | string | |
extracted_text | string | New text without quoted history (set by the gatekeeper) |
preview | string | |
snippet | string | Excerpt around the match. Only in search results, and empty for a key holding message_metadata without message_read |
labels | string[] | |
in_reply_to | string | |
references | string[] | |
status | received | queued | sending | sent | failed | held | cancelled | |
delivery_error_code | unauthorized_ip | auth_failed | relay_unreachable | connection_refused | tls_failed | unknown | Why an outbound message did not leave. Set when a delivery attempt was refused, absent otherwise; with status failed, the message is not delivered and the console says so. |
delivery_error | string | What the mail server said when it refused the message, one line, at most 500 characters, never a credential. |
last_error | string | The last reason a message did not leave, from the transport or from the platform: a send held by undo send whose window closed while sending was paused fails with the reason of the pause here, and no delivery_error, because no mail server refused it. |
cancel_window_ends_at | string (date-time) | End of the undo window of a send born held. Before it, POST .../cancel withdraws the message; after it, the message is on its way and cancel answers 409 invalid_state. Absent on a message that never had a window. |
authentication_results | object | What the SMTP edge verified for inbound mail: dkim, spf, dmarc, dmarc_policy, antivirus, spam_score, spam_action. Mail failing DMARC is labelled dmarc-fail, and spam under p=quarantine or p=reject. |
gatekeeper | object | |
screening | review | allowed | released | Hold state under screen before notify; absent when the message was not held. Pending messages are never returned. |
attachments | object[] | |
created_at | string (date-time) |
MessageList object
| Name | Type | Notes |
|---|---|---|
count | integer | |
next_page_token | string | |
messages | Message[] |
Attachment object
| Name | Type | Notes |
|---|---|---|
filename | string | |
content_type | string | |
content | string (base64) | required |
Send object
| Name | Type | Notes |
|---|---|---|
to | string (email)[] | required, at least 1 item |
cc | string (email)[] | |
bcc | string (email)[] | |
subject | string | |
text | string | |
html | string | |
labels | string[] | |
attachments | Attachment[] |
Reply object
| Name | Type | Notes |
|---|---|---|
text | string | |
html | string | |
reply_all | boolean | |
labels | string[] | |
attachments | Attachment[] |
SendResult object
| Name | Type | Notes |
|---|---|---|
message_id | string | |
thread_id | string (uuid) | |
status | queued | held | sent | suppressed | State of the row the request created. held means the undo window is open: nothing has been attempted yet, and cancel may still withdraw it. suppressed means every recipient is on the suppression list: no message exists because there was nobody to write to. |
cancel_window_ends_at | string (date-time) | Deadline of POST .../cancel, present when status is held. |
recipients | RecipientOutcome[] | One entry per recipient of the request, in the order it named them. A suppressed recipient is reported here and not hidden: the message is never written for it. |
CreateDraft object
| Name | Type | Notes |
|---|---|---|
to | string (email)[] | |
cc | string (email)[] | |
bcc | string (email)[] | |
subject | string | |
text | string | |
html | string | |
labels | string[] | |
attachments | Attachment[] | |
in_reply_to | string | Message UUID or RFC Message-ID to reply to |
reply_all | boolean | With in_reply_to: copy the other recipients |
send_at | string (date-time) | Send automatically at this time (future, within 365 days) |
UpdateDraft object
| Name | Type | Notes |
|---|---|---|
to | string (email)[] | |
cc | string (email)[] | |
bcc | string (email)[] | |
subject | string | |
text | string | |
html | string | |
labels | string[] | |
attachments | Attachment[] | |
send_at | string,null (date-time) | null cancels a scheduled send |
Draft object
| Name | Type | Notes |
|---|---|---|
draft_id | string (uuid) | |
inbox_id | string (email) | |
thread_id | string (uuid) | Set for a reply draft |
in_reply_to | string | RFC Message-ID of the message replied to |
references | string[] | |
to | string[] | |
cc | string[] | |
bcc | string[] | |
subject | string | |
text | string | |
html | string | |
labels | string[] | |
attachments | object[] | |
send_at | string (date-time) | |
status | draft | scheduled | failed | |
last_error | string | Why a scheduled send was refused |
created_at | string (date-time) | |
updated_at | string (date-time) |
DraftList object
| Name | Type | Notes |
|---|---|---|
count | integer | |
next_page_token | string | |
drafts | Draft[] |
Webhook object
| Name | Type | Notes |
|---|---|---|
webhook_id | string (uuid) | |
url | string | |
event_types | EventType[] | |
inbox_ids | string[] | |
enabled | boolean | |
secret | string | whsec_..., returned only on create |
Domain object
| Name | Type | Notes |
|---|---|---|
domain_id | string (uuid) | |
domain | string | |
shared | boolean | |
status | pending | verified | |
records | object[] | |
last_checked_at | string (date-time) | |
verified_at | string (date-time) | |
catch_all_inbox | string (email) | The inbox that receives mail addressed to a local part nobody created on this domain, or null for no catch-all (D5) |
PushSubscribe object
| Name | Type | Notes |
|---|---|---|
endpoint | string (uri) | required, The device at its push service. A capability: never logged, never returned |
keys | object | required |
expiration_time | integer | Milliseconds since the epoch, or null |
user_agent | string | The browser that subscribed, for the device label, max length 512 |
PushUnsubscribe object
| Name | Type | Notes |
|---|---|---|
endpoint | string (uri) | required, The endpoint the browser is dropping |
PushSubscription object
| Name | Type | Notes |
|---|---|---|
push_subscription_id | string (uuid) | |
device | string | Label derived from the user agent, for example iPhone |
user_agent | string | |
created_at | string (date-time) | |
last_used_at | string (date-time) |
PushSubscriptionList object
| Name | Type | Notes |
|---|---|---|
count | integer | |
push_subscriptions | PushSubscription[] |
Ticket object
| Name | Type | Notes |
|---|---|---|
id | string | |
number | integer | |
inbox_id | string | |
thread_id | string | |
subject | string | |
requester | string | |
state | TicketState | |
priority | TicketPriority | |
assignee_user | string | |
assignee_key | string | |
sla_policy | string | |
due_first_reply | string (date-time) | |
due_resolution | string (date-time) | |
first_reply_at | string (date-time) | |
resolved_at | string (date-time) | |
closed_at | string (date-time) | |
tags | string[] | |
created_at | string (date-time) | |
updated_at | string (date-time) | |
body_available | boolean | Whether the thread still has messages. False after retention emptied it; the ticket stays readable and retention_notice says why. Present on a single ticket read. |
retention_notice | string | The sentence explaining that retention deleted the conversation. |
TicketList object
| Name | Type | Notes |
|---|---|---|
count | integer | |
next_page_token | string | |
tickets | Ticket[] |
TicketEvent object
| Name | Type | Notes |
|---|---|---|
id | integer | |
kind | string | |
actor | string | |
detail | object | |
created_at | string (date-time) |
TicketNote object
| Name | Type | Notes |
|---|---|---|
id | string | |
author | string | |
body | string | |
created_at | string (date-time) |
TicketDetail object
| Name | Type | Notes |
|---|---|---|
ticket | Ticket | |
events | TicketEvent[] | |
notes | TicketNote[] |
TicketStats object
| Name | Type | Notes |
|---|---|---|
by_state | object | |
open | integer | |
breaching | integer | |
unassigned | integer | |
median_first_reply_seconds | integer |
CreateTicket object
| Name | Type | Notes |
|---|---|---|
thread_id | string | required, The conversation to open the ticket on. A thread whose previous ticket is resolved or closed accepts a new ticket, with a new number. |
priority | TicketPriority | |
tags | string[] | |
assignee_user | string | |
assignee_key | string |
UpdateTicket object
| Name | Type | Notes |
|---|---|---|
state | TicketState | |
priority | TicketPriority | |
tags | string[] | |
assignee_user | string | |
assignee_key | string |
TicketViewFilters object
| Name | Type | Notes |
|---|---|---|
state | TicketState | |
priority | TicketPriority | |
assignee | string | user id, API key id, or none for unassigned |
inbox | string | inbox address |
q | string | case insensitive over subject and requester |
TicketView object
| Name | Type | Notes |
|---|---|---|
view_id | string | |
name | string | |
shared | boolean | true when the whole organization sees it |
owner_user_id | string | The person the view belongs to; absent on a shared view |
filters | TicketViewFilters | |
created_at | string (date-time) |
TicketViewList object
| Name | Type | Notes |
|---|---|---|
count | integer | |
ticket_views | TicketView[] |
CreateTicketView object
| Name | Type | Notes |
|---|---|---|
name | string | required, Up to 60 characters, unique among the views the caller can see |
shared | boolean | Save it for the whole organization; an owner or an admin only |
filters | TicketViewFilters | required |
TicketSLAWindow object
| Name | Type | Notes |
|---|---|---|
weekday | integer | required, ISO weekday: 1 is Monday, 7 is Sunday, min 1, max 7 |
start | string | required, HH:MM in the window timezone |
end | string | required, HH:MM, after start; a window never crosses midnight |
timezone | string | required, A real IANA zone, for example Europe/Berlin |
TicketSLATargets object
| Name | Type | Notes |
|---|---|---|
first_reply | object | |
resolution | object |
TicketSLA object
| Name | Type | Notes |
|---|---|---|
windows | TicketSLAWindow[] | |
targets | TicketSLATargets | |
auto_close_days | integer | Days a resolved ticket may stay silent before it closes; 0 disables |
source | default | organization | default means the plan table, untouched |
updated_by | string | |
updated_at | string (date-time) | |
note | string | The sentence that says a change is not retroactive |
UpdateTicketSLA object
| Name | Type | Notes |
|---|---|---|
windows | TicketSLAWindow[] | |
targets | TicketSLATargets | |
auto_close_days | integer | min 0, max 365 |
Checkout object
| Name | Type | Notes |
|---|---|---|
plan | builder | growth | team | required |
interval | monthly | yearly | default monthly |
success_url | string (uri) | Defaults to the deployment's MAI_BILLING_RETURN_URL |
cancel_url | string (uri) | Defaults to the deployment's MAI_BILLING_RETURN_URL |
BillingSession object
| Name | Type | Notes |
|---|---|---|
session_id | string | |
url | string (uri) |
Billing object
| Name | Type | Notes |
|---|---|---|
plan | free | builder | growth | team | |
status | none | incomplete | incomplete_expired | trialing | active | past_due | unpaid | canceled | paused | |
interval | monthly | yearly | |
current_period_end | string (date-time) | |
cancel_at_period_end | boolean | |
past_due_since | string (date-time) | |
grace_period_ends_at | string (date-time) | Set while past_due or unpaid; the plan drops to free after it |
Member object
| Name | Type | Notes |
|---|---|---|
user_id | string (uuid) | |
email | string (email) | |
name | string | |
role | owner | admin | member | |
last_login_at | string (date-time) | |
created_at | string (date-time) | |
inbox_count | integer | How many inboxes this person can see |
invited_at | string (date-time) | |
accepted_at | string (date-time) |
MemberList object
| Name | Type | Notes |
|---|---|---|
count | integer | |
members | Member[] |
Invitation object
| Name | Type | Notes |
|---|---|---|
invitation_id | string (uuid) | |
email | string (email) | |
role | admin | member | |
invited_by | string | |
expires_at | string (date-time) | |
created_at | string (date-time) |
InvitationList object
| Name | Type | Notes |
|---|---|---|
count | integer | |
invitations | Invitation[] |
InvitationPreview object
| Name | Type | Notes |
|---|---|---|
organization | string | |
email | string (email) | |
role | admin | member | |
expires_at | string (date-time) |
Group object
| Name | Type | Notes |
|---|---|---|
group_id | string (uuid) | |
name | string | |
slug | string | |
color | string | |
inbox_count | integer | |
domain_count | integer | |
created_at | string (date-time) |
KeyScopeInput object
| Name | Type | Notes |
|---|---|---|
kind | org | domain | user | Defaults to org for the owner and an admin, to user for a member. A member may only ask for user. |
id | string (uuid) | The domain, when kind is domain |
ApiKey object
| Name | Type | Notes |
|---|---|---|
api_key_id | string (uuid) | |
api_key | string | The secret, returned once on create |
prefix | string | |
name | string | |
permissions | Permission[] | Null means every permission the role allows |
scope | object | |
expires_at | string (date-time) | |
last_used_at | string (date-time) | |
created_at | string (date-time) | |
request_count | integer | Requests this key has authenticated |
inactive_since | string (date-time) | The last use, or the creation of a key never used |
stale | boolean | True when the key has not been used for 30 days. The operator command mai admin keys-stale applies the same rule and can revoke such keys. |
GroupRef object
| Name | Type | Notes |
|---|---|---|
group_id | string (uuid) | |
name | string | |
color | string |
GroupList object
| Name | Type | Notes |
|---|---|---|
count | integer | |
groups | Group[] |
Grant object
| Name | Type | Notes |
|---|---|---|
user_id | string (uuid) | |
email | string (email) | |
name | string | |
rights | viewer | responder | |
created_at | string (date-time) |
GrantList object
| Name | Type | Notes |
|---|---|---|
count | integer | |
grants | Grant[] |
OrgExport object
| Name | Type | Notes |
|---|---|---|
format | string | |
generated_at | string (date-time) | |
organization | Org | |
inboxes | object[] | |
domains | object[] | |
webhooks | object[] | |
api_keys | object[] | Metadata only; the key and its hash are never exported. |
counts | object | |
audit_log | object[] | |
notice | string | What this export deliberately leaves out, in plain language |
SendingThresholds object
| Name | Type | Notes |
|---|---|---|
max_bounce_rate | number | Hard bounce rate above which sending pauses. |
max_complaint_rate | number | Complaint rate above which sending pauses, once min_complaint_sends is reached. |
min_sends | integer | Sends needed in the window before the bounce rate applies. |
min_complaint_sends | integer | Sends needed before the complaint rate applies; below it the limit is a count. |
small_sender_complaints | integer | Complaints in the window that pause sending below min_complaint_sends. |
WebhookEvent object
| Name | Type | Notes |
|---|---|---|
event_id | string (uuid) | |
event_type | string | |
status | pending | succeeded | failed | Delivery state |
attempts | integer | Attempts claimed so far |
last_attempt_status | succeeded | failed | |
last_status_code | integer | |
last_latency_ms | integer | |
last_attempt_at | string (date-time) | |
created_at | string (date-time) |
WebhookEventList object
| Name | Type | Notes |
|---|---|---|
count | integer | |
events | WebhookEvent[] | |
next_page_token | string |
WebhookAttempt object
| Name | Type | Notes |
|---|---|---|
attempt_no | integer | |
status | succeeded | failed | |
status_code | integer | Null when the connection failed |
latency_ms | integer | |
response_excerpt | string | At most 2 KB of the response body with credentials redacted; response headers are never stored, max length 2048 |
error | string | |
created_at | string (date-time) |
WebhookAttemptList object
| Name | Type | Notes |
|---|---|---|
count | integer | |
attempts | WebhookAttempt[] |
WebhookReplay object
| Name | Type | Notes |
|---|---|---|
event_id | string (uuid) | |
delivery_id | integer | |
status | pending | It is queued; the attempt follows |
attempts | integer | Attempts recorded before this replay |
RotateSecret object
| Name | Type | Notes |
|---|---|---|
webhook_id | string (uuid) | |
secret | string | The new signing secret, shown once |
rotated_at | string (date-time) | |
previous_secret_valid_until | string (date-time) | Until then the dispatcher signs with the previous secret too |
MFAStatus object
| Name | Type | Notes |
|---|---|---|
enrolled | boolean | A confirmed factor; a pending secret does not count |
confirmed_at | string (date-time) | |
recovery_codes_left | integer | min 0, max 10 |
required_by_organization | boolean | The organization requires a factor; with enrolled false every other route answers 403 mfa_enrollment_required |
organization_policy | MFAPolicy |
MFAPolicy object
| Name | Type | Notes |
|---|---|---|
require_mfa | boolean |
MFAEnrollment object
| Name | Type | Notes |
|---|---|---|
secret | string | Base32 TOTP secret, for manual entry |
otpauth_url | string | otpauth:// URI a QR code is drawn from (SHA1, 6 digits, 30 seconds) |
recovery_codes | string[] | Ten single use codes; only their hashes are stored, at least 10 item, at most 10 items |
MFAVerify object
| Name | Type | Notes |
|---|---|---|
enrolled | boolean | |
recovery_codes_left | integer | |
session | Session |
MFAStep object
| Name | Type | Notes |
|---|---|---|
mfa_required | true | |
challenge_token | string | Single use, five minutes, five attempts; redeem at POST /v0/auth/mfa/challenge |
kind | verify | enroll | verify when the factor exists, enroll when the organization requires one this person has not set up |
expires_at | string (date-time) |
SearchResult object
| Name | Type | Notes |
|---|---|---|
count | integer | |
next_cursor | string | Pass as cursor= for the next page; absent on the last one |
messages | Message[] |
Forward object
| Name | Type | Notes |
|---|---|---|
to | string (email)[] | required, at least 1 item |
cc | string (email)[] | |
bcc | string (email)[] | |
comment | string | Optional text above the quoted message, below the signature |
labels | string[] |
BatchResult object
| Name | Type | Notes |
|---|---|---|
applied | integer | Lines whose action happened (or had already happened: every action is idempotent) |
failed | integer | Lines with a per item failure: not_found, forbidden or invalid |
results | BatchItem[] |
BatchItem object
| Name | Type | Notes |
|---|---|---|
id | string | The id as it was sent |
status | applied | not_found | forbidden | invalid | |
error | string | Why this item did not apply; absent on applied |
RuleTarget object
| Name | Type | Notes |
|---|---|---|
kind | inbox | domain | org | required |
id | string (uuid) | The inbox or the domain id; absent for kind org |
RuleSize object
| Name | Type | Notes |
|---|---|---|
larger | integer (int64) | min 0 |
smaller | integer (int64) | min 0 |
RuleConditions object
| Name | Type | Notes |
|---|---|---|
from | string[] | Sender, case and accent insensitive substring, ORed, at most 32 items |
to | string[] | Recipient (To or Cc), ORed, at most 32 items |
subject | string[] | Subject substring, ORed, at most 32 items |
query | string | Full text, same parser as search, max length 512 |
negated_query | string | The message must not match this, max length 512 |
has_attachment | boolean | |
size | RuleSize | |
spam_verdict | clean | review | spam[] | at most 3 items, one of clean, review, spam |
RuleAction object
| Name | Type | Notes |
|---|---|---|
type | add_label | remove_label | archive | move_to_folder | mark_read | forward_to | auto_reply | reject | webhook | assign | required |
label | string | add_label, remove_label, move_to_folder: the label, which is also the folder, max length 64 |
address | string (email) | forward_to: a verified address of the organization |
reason | string | reject: one line, no address, sent back in the 550, max length 200 |
webhook_id | string (uuid) | webhook: the endpoint that receives rule.matched |
subject | string | auto_reply: the subject of the answer |
text | string | auto_reply: the body of the answer, max length 4000 |
assignee | string (uuid) | assign: the member who takes the ticket |
Rule object
| Name | Type | Notes |
|---|---|---|
rule_id | string (uuid) | |
name | string | |
target | RuleTarget | |
priority | integer | Lower is evaluated first; ties go to the older rule |
enabled | boolean | |
include_spam | boolean | False means the rule never sees a message with the spam verdict |
conditions | RuleConditions | |
actions | RuleAction[] | at most 20 items |
created_at | string (date-time) | |
updated_at | string (date-time) |
RuleInput object
| Name | Type | Notes |
|---|---|---|
name | string | required, max length 100 |
target | RuleTarget | |
priority | integer | min 0, max 100000, default 100 |
enabled | boolean | default true |
include_spam | boolean | default false |
conditions | RuleConditions | required |
actions | RuleAction[] | required, at least 1 item, at most 20 items |
RulePatch object
| Name | Type | Notes |
|---|---|---|
name | string | max length 100 |
target | RuleTarget | |
priority | integer | min 0, max 100000 |
enabled | boolean | |
include_spam | boolean | |
conditions | RuleConditions | |
actions | RuleAction[] | at least 1 item, at most 20 items |
RuleApplyReport object
| Name | Type | Notes |
|---|---|---|
job_id | string (uuid) | |
rule_id | string (uuid) | |
status | pending | running | done | |
matched | integer | |
applied | integer | |
skipped | object | |
created_at | string (date-time) | |
finished_at | string (date-time) |
AgentAction object
| Name | Type | Notes |
|---|---|---|
name | draft_reply | draft_forward | add_labels | assign_thread | mark_spam | archive_thread | move_to_trash | |
route | string | The API operation a person uses to do the same thing |
summary | string |
AgentConfig object
| Name | Type | Notes |
|---|---|---|
enabled | boolean | The effective state: the switch on and at least one action. An empty action set is off. |
configured | boolean | False when this inbox was never configured; the rest of the answer is then the default form |
disabled_reason | not_configured | no_actions | switched_off | Absent when the agent is on |
instructions | string | Empty when none are stored |
tone | concise_professional | concise_casual | expanded_professional | expanded_casual | |
allowed_actions | draft_reply | draft_forward | add_labels | assign_thread | mark_spam | archive_thread | move_to_trash[] | The whole set, in policy order; empty means the agent is off, one of draft_reply, draft_forward, add_labels, assign_thread, mark_spam, archive_thread, move_to_trash |
available_actions | AgentAction[] | The vocabulary a configuration may name |
budget_per_hour | integer | Actions this inbox may take in one hour |
actions_used | integer | Actions the agent took in the current hour |
agent_key_id | string (uuid) | The key that configured the agent: its identity in the audit log |
agent_key_name | string | |
updated_at | string (date-time) |
AgentConfigRequest object
| Name | Type | Notes |
|---|---|---|
instructions | string | 1 to 4000 characters, or null to clear, max length 4000 |
allowed_actions | draft_reply | draft_forward | add_labels | assign_thread | mark_spam | archive_thread | move_to_trash[] | The whole set; an empty array turns the agent off, one of draft_reply, draft_forward, add_labels, assign_thread, mark_spam, archive_thread, move_to_trash |
tone | concise_professional | concise_casual | expanded_professional | expanded_casual | |
enabled | boolean |
AutoReply object
| Name | Type | Notes |
|---|---|---|
inbox | string (email) | |
rule_id | string (uuid) | The one ordinary rule that carries the answer; the guards are its guards |
mode | fixed | daily | weekly | monthly | permanent | |
timezone | string | The IANA zone the window is read in, always explicit |
start | string | RFC 3339 instant for fixed, HH:MM clock time for daily, weekly and monthly |
end | string | The other end of the window; an end before the start wraps past midnight |
days | string[] | Weekday tokens (mon..sun) in weekly mode, day-of-month numbers in monthly mode, empty otherwise |
subject | string | |
body_text | string | |
body_html | string | |
restrict_to_domain | boolean | Answer only senders on the inbox's own domain or on a domain the organization verified |
active | boolean | Whether the window covers now, in the schedule's timezone |
updated_at | string (date-time) |
AutoReplyInput object
| Name | Type | Notes |
|---|---|---|
mode | fixed | daily | weekly | monthly | permanent | required |
timezone | string | required, IANA zone, for example Europe/Rome; required, never derived from the server |
start | string | RFC 3339 for fixed, HH:MM for daily, weekly and monthly; absent for permanent |
end | string | RFC 3339 for fixed, HH:MM for daily, weekly and monthly; absent for permanent |
days | string[] | Weekday tokens for weekly, day-of-month numbers for monthly, absent otherwise, at most 31 items |
subject | string | required, max length 256 |
body_text | string | required, max length 4000 |
body_html | string | |
restrict_to_domain | boolean | default false |
BlocklistEntry object
| Name | Type | Notes |
|---|---|---|
entry_id | string (uuid) | |
scope | inbox | org | |
scope_id | string (uuid) | The inbox, absent for the organization list |
inbox | string (email) | |
list_kind | block | allow | allow beats block: an allowed address passes through a blocked domain |
kind | sender | domain | |
value | string | The lowered address or domain, matched exactly |
created_at | string (date-time) |
BlocklistInput object
| Name | Type | Notes |
|---|---|---|
list_kind | block | allow | default block |
kind | sender | domain | required |
value | string | required, An address for kind sender, a domain for kind domain; never one as the other, max length 320 |
MessageTimeline object
| Name | Type | Notes |
|---|---|---|
message_id | string (uuid) | |
inbox_id | string | The address that holds the message, never the list of recipients |
status | received | queued | sending | sent | failed | held | cancelled | The state of the row now; the events carry how it got there |
events | MessageTimelineEvent[] |
MessageTimelineEvent object
| Name | Type | Notes |
|---|---|---|
type | message.received | message.received.spam | message.sent | message.delivered | message.delivery_delayed | message.failed | message.rejected | message.bounced | message.complained | message.dropped | message.ruled | |
source | smtp | dsn | arf | breaker | rspamd | rule | smtp a reply, dsn a delivery status notification, arf a feedback report, breaker the sending health breaker, rspamd the spam filter, rule a deterministic rule |
code | string | The SMTP status, the DSN status or the policy code; empty when the source has none. unknown always carries a reason in text. |
text | string | The reply or the sentence, one line, at most 500 characters, never a body and never a recipient |
at | string (date-time) |
DomainReputation object
| Name | Type | Notes |
|---|---|---|
domain | string | |
window_hours | integer | The rolling window the counts cover, the same as sending_health.window_hours |
sends | integer | Messages that reached a final outcome in the window |
hard_bounces | integer | Permanent rejections at send time plus bounces reported by a DSN |
complaints | integer | Spam complaints reported through the feedback loop |
bounce_rate | number | hard_bounces divided by sends, 0 when sends is 0 |
complaint_rate | number | complaints divided by sends, 0 when sends is 0 |
thresholds | SendingThresholds | |
breach_state | ok | breached | breached when the window trips a limit or when the breaker has paused an inbox on the domain or the organization |
breach_reason | string | The sentence naming the window and the limit crossed; absent while breach_state is ok |
paused_scope | string | What paused sending: inbox, organization, or empty. A send is refused by both scopes, so this tells them apart. |
paused_at | string (date-time) | |
paused_reason | string | The sentence of the pause, the same one the 403 sending_paused carries |
OAuthClientInput object
| Name | Type | Notes |
|---|---|---|
client_name | string | required, max length 100 |
client_uri | string (uri) | Where a person can read about the app |
redirect_uris | string (uri)[] | required, https, or http on a loopback host; no fragment, at least 1 item, at most 10 items |
scope | messages:send | full_access | The most this client may ever ask for, default messages:send |
OAuthClient object
| Name | Type | Notes |
|---|---|---|
client_id | string | Opaque, prefixed oac_ |
client_name | string | |
client_uri | string | |
redirect_uris | string[] | |
scopes | string[] | The scopes this client may ask for |
created_at | string (date-time) | |
last_used_at | string (date-time) |
OAuthToken object
| Name | Type | Notes |
|---|---|---|
access_token | string | Opaque, prefixed mai_oat_; lives one hour |
token_type | Bearer | |
expires_in | integer | Seconds |
refresh_token | string | Opaque, prefixed mai_ort_; rotated on every use, lives thirty days |
scope | messages:send | full_access |
OAuthGrant object
| Name | Type | Notes |
|---|---|---|
grant_id | string (uuid) | |
client_id | string | |
client_name | string | |
client_uri | string | |
scope | messages:send | full_access[] | one of messages:send, full_access |
created_at | string (date-time) | |
last_used_at | string (date-time) | |
mfa_protected | boolean | The organization requires a second factor, so an app could only be connected with one |
OAuthGrantList object
| Name | Type | Notes |
|---|---|---|
count | integer | |
grants | OAuthGrant[] |
RequestLogEntry object
| Name | Type | Notes |
|---|---|---|
id | string (uuid) | |
request_id | string | The X-Request-Id of the call |
key_id | string (uuid) | The key that made the call; absent for a person signed in |
actor | string | user:<id> or api_key:<id> |
actor_type | user | agent | |
method | string | |
path | string | Path and filters, with the value of a credential-shaped query parameter masked |
status | integer | |
duration_ms | integer | |
sdk | string | SDK name normalised from the User-Agent; absent when the caller is not one of our SDKs |
sdk_version | string | |
sdk_update_available | boolean | A newer version of that SDK exists on this deployment |
sdk_latest | string | |
sdk_notice | string | The sentence to show a person: which SDK, which version, which one to move to |
created_at | string (date-time) |
RequestLogBody object
| Name | Type | Notes |
|---|---|---|
headers | object | Request headers only, from an allowlist |
body | object | The JSON as it was, with credentials and mail content replaced by markers |
bytes | integer | Size of the original body |
sha256 | string | Digest of the original body, empty when there was none |
truncated | boolean | The stored body is a summary: the original was larger than the log keeps |
RetentionPolicy object
| Name | Type | Notes |
|---|---|---|
messages_days | integer | 0 keeps messages forever; otherwise 1 to 3650 days. Deleting a message also queues its raw blob and its attachments' blobs., min 0, max 3650 |
attachments_days | integer | 0 keeps attachments forever; otherwise 1 to 3650 days, independent of messages_days., min 0, max 3650 |
audit_days | integer | 0 keeps audit entries forever; otherwise 30 to 3650 days, default 90., min 0, max 3650 |
Suppression object
| Name | Type | Notes |
|---|---|---|
email | string (email) | The address, lower cased |
origin | bounce | complaint | manual | Why it is on the list: a permanent bounce, a complaint (ARF report), or a person (the API or a one-click unsubscribe) |
source_id | string | Provenance: the message or report the entry came from; absent for a manual entry |
created_at | string (date-time) |
SuppressionInput object
| Name | Type | Notes |
|---|---|---|
email | string (email) | required |
origin | bounce | complaint | manual | required, Required: a row that does not say why the address is there cannot be reviewed |
source_id | string | max length 320 |
SuppressionsPost object
| Name | Type | Notes |
|---|---|---|
entries | SuppressionInput[] | The batch form, 1 to 100 entries; the CSV import is the way to add more, at least 1 item, at most 100 items |
email | string (email) | The single entry form: email and origin, without entries |
origin | bounce | complaint | manual | |
source_id | string | max length 320 |
SuppressionResult object
| Name | Type | Notes |
|---|---|---|
added | integer | Rows the list gained |
existing | integer | Addresses the list already knew; the origin of an existing row is kept |
count | integer | |
entries | Suppression[] | The entries as they are stored, not as they were sent |
RecipientOutcome object
| Name | Type | Notes |
|---|---|---|
address | string (email) | |
status | queued | suppressed | suppressed: the address is on the suppression list of the organization, so the message was not written for it |
CustomRole object
| Name | Type | Notes |
|---|---|---|
role_id | string (uuid) | |
name | string | 1 to 60 characters, unique in the organization ignoring case |
description | string | At most 200 characters; empty when none was given |
permissions | Permission[] | Names an API key could hold. A custom role that lists nothing is refused on the write path. |
member_count | integer | How many people the role reaches now, counting a person once even when several of their groups carry it |
assignments | RoleAssignment[] | The people and the groups that hold it |
created_at | string (date-time) | |
updated_at | string (date-time) |
CustomRoleList object
| Name | Type | Notes |
|---|---|---|
count | integer | |
roles | CustomRole[] |
CustomRoleRequest object
| Name | Type | Notes |
|---|---|---|
name | string | max length 60 |
description | string | max length 200 |
permissions | Permission[] | Required on create. An unknown name is 422 unknown_permission; a name the caller does not hold is 403 permission_escalation. |
RoleAssignment object
| Name | Type | Notes |
|---|---|---|
role_id | string (uuid) | |
user_id | string (uuid) | Set when the role is assigned to a person |
email | string (email) | The address of that person, so the console does not resolve ids |
group_id | string (uuid) | Set when the role is assigned to a group |
group_name | string | The brand's display name |
assigned_at | string (date-time) |
RoleAssignmentRequest object
| Name | Type | Notes |
|---|---|---|
role_ids | string (uuid)[] | required, The whole set the subject should hold. An empty array revokes every custom role from the subject; that is a revocation, not a mistake., at most 20 items |
RoleAssignmentChange object
| Name | Type | Notes |
|---|---|---|
assignments | RoleAssignment[] | |
added | string (uuid)[] | Role ids this call gave |
removed | string (uuid)[] | Role ids this call took away |
ExportJob object
| Name | Type | Notes |
|---|---|---|
export_id | string (uuid) | |
status | queued | running | ready | failed | |
row_count | integer | Rows the document will contain, counted inside the caller's scope; the number that decided it was a job |
size_bytes | integer | Size of the document, once it exists |
created_at | string (date-time) | |
ready_at | string (date-time) | |
expires_at | string (date-time) | End of the seven day download window; after it the job and its document are purged |
page_url | string | A page on the site that shows this export, for a person who does not hold a key |
poll_url | string | GET this to follow the job, on the documented version |
download_url | string | GET this once status is ready |
error | string | Why the job failed, one line, when status is failed |
FeatureState object
| Name | Type | Notes |
|---|---|---|
name | rules | agent | oauth | request_logs | |
enabled | boolean | The feature works for this organization: deployment and organization both on |
deployment | boolean | The kill switch of this deployment (MAI_RULES_ENABLED, MAI_AGENT_ENABLED, MAI_OAUTH_ENABLED, MAI_REQUEST_LOGS_ENABLED) |
organization | boolean | The platform operator's choice for this organization; off by default |
OrganizationFeatures object
| Name | Type | Notes |
|---|---|---|
rules | FeatureState | |
agent | FeatureState | |
oauth | FeatureState | |
request_logs | FeatureState |
Error object
| Name | Type | Notes |
|---|---|---|
name | string | required |
code | missing_authorization | unknown_api_key | validation_error | not_found | already_exists | domain_not_verified | conflict | cannot_delete | limit_exceeded | rate_limit_exceeded | rate_limited | address_quarantined | sending_paused | missing_permission | permission_escalation | api_key_expired | not_held | billing_disabled | billing_provider_error | already_subscribed | no_billing_account | invalid_signature | too_many_streams | payload_too_large | attachment_infected | antivirus_unavailable | auth_disabled | provider_not_configured | invalid_magic_link | invalid_state | code_rejected | email_not_verified | provider_unavailable | email_delivery_failed | invalid_refresh_token | token_expired | unknown_token | session_revoked | access_denied | internal_error | organization_exists | needs_organization | session_required | thread_required | thread_not_found | ticket_not_found | ticket_exists | invalid_transition | invalid_priority | note_required | no_inbound_message | view_not_found | view_exists | too_many_views | push_disabled | too_many_subscriptions | mfa_required | mfa_enrollment_required | mfa_step_mismatch | invalid_mfa_challenge | invalid_mfa_code | mfa_code_reused | invalid_recovery_code | too_many_attempts | already_enrolled | not_enrolled | mfa_required_by_organization | mfa_reset_required | unknown_action | oauth_disabled | invalid_client | invalid_grant | invalid_request | invalid_scope | unsupported_response_type | invalid_redirect_uri | retention_out_of_range | unknown_permission | app_forbidden | insufficient_rights | required |
message | string | required |
delivery_error_code | unauthorized_ip | auth_failed | relay_unreachable | connection_refused | tls_failed | unknown | Why the transport refused a send (502 email_delivery_failed only). unauthorized_ip is an SMTP 525, auth_failed a 535. |
delivery_error | string | What the mail server said, in its own words, at most 500 characters, never a credential. |
message_id | string | The RFC Message-ID of the message the refusal is recorded on. |
thread_id | string (uuid) | The thread of that message, where the console shows it as Not delivered. |