---
title: "Objects"
description: "Every object the MachineMail API accepts or returns, field by field, with types and required fields. Endpoint pages link here for nested objects."
canonical: "https://machinemail.dev/docs/reference/api/objects"
---

# Objects

> Every object the MachineMail API accepts or returns, field by field, with types and required fields. Endpoint pages link here for nested objects.

Source: https://machinemail.dev/docs/reference/api/objects

Endpoint pages link here for nested objects. Fields marked required are always present in responses and must be sent in requests.

## AgentIdentityRequest

| Field | Type | | Description |
| --- | --- | --- | --- |
| `type` | `"anonymous" \| "service_auth" \| "identity_assertion"` | required | Registration type. |
| `name` | `string` |  | Agent label (first 100 characters). Untrusted; shown as agent-described in the dashboard. |
| `client_name` | `string` |  | Alias of name. |
| `login_hint` | `string` |  | service_auth: email address of the user the agent acts for. Required for that type. |
| `assertion_type` | `string` |  | identity_assertion: urn:ietf:params:oauth:token-type:id-jag. Required for that type. |
| `assertion` | `string` |  | identity_assertion: the ID-JAG (signed JWT). Required for that type. |

## AgentIdentityResponse

| Field | Type | | Description |
| --- | --- | --- | --- |
| `registration_id` | `string` | required |  |
| `agent_id` | `string` | required | Same value as registration_id. |
| `registration_type` | `"anonymous" \| "service_auth" \| "identity_assertion"` | required | anonymous: no human yet; a sandbox inbox until a human claims it. service_auth: on behalf of a named user, who confirms first. identity_assertion: an ID-JAG from a trusted agent provider. |
| `identity_assertion` | `string` |  | Service-signed JWT to exchange at /oauth2/token. Absent for service_auth until the claim completes. |
| `assertion_expires` | `string (date-time)` |  |  |
| `scopes` | `("inboxes.read" \| "inboxes.write" \| "messages.read" \| "messages.send" \| "messages.write" \| "webhooks.read" \| "webhooks.write" \| "domains.read" \| "domains.write" \| "lists.read" \| "lists.write" \| "metrics.read" \| "pods.read" \| "pods.write" \| "api_keys.read" \| "api_keys.write")[]` |  | identity_assertion registrations: the granted scopes. |
| `pre_claim_scopes` | `("inboxes.read" \| "inboxes.write" \| "messages.read" \| "messages.send" \| "messages.write" \| "webhooks.read" \| "webhooks.write" \| "domains.read" \| "domains.write" \| "lists.read" \| "lists.write" \| "metrics.read" \| "pods.read" \| "pods.write" \| "api_keys.read" \| "api_keys.write")[]` |  | anonymous registrations: scopes before a human claims the agent (its sandbox inbox only). |
| `post_claim_scopes` | `("inboxes.read" \| "inboxes.write" \| "messages.read" \| "messages.send" \| "messages.write" \| "webhooks.read" \| "webhooks.write" \| "domains.read" \| "domains.write" \| "lists.read" \| "lists.write" \| "metrics.read" \| "pods.read" \| "pods.write" \| "api_keys.read" \| "api_keys.write")[]` |  | Scopes after the claim. |
| `claim_url` | `string` |  | Where to start the claim ceremony (/agent/identity/claim). |
| `claim_token` | `string` |  | Secret for /agent/identity/claim and the claim grant. |
| `claim_token_expires` | `string (date-time)` |  |  |
| `claim` | `object` |  | service_auth registrations: the ceremony is already started. |
| `inbox` | `object` |  | anonymous registrations: the ephemeral sandbox inbox. There is no API key; authenticate with the access token from /oauth2/token. |

## AgentRegistration

An agent registration. Hashes and tokens are never included.

| Field | Type | | Description |
| --- | --- | --- | --- |
| `agent_id` | `string` | required | ID of the registration (reg_...). |
| `registration_id` | `string` | required | Same value as agent_id. |
| `registration_type` | `"anonymous" \| "service_auth" \| "identity_assertion"` | required | anonymous: no human yet; a sandbox inbox until a human claims it. service_auth: on behalf of a named user, who confirms first. identity_assertion: an ID-JAG from a trusted agent provider. |
| `status` | `"pending_claim" \| "claimed" \| "revoked"` | required |  |
| `name` | `string` |  | Agent-described label. Untrusted. |
| `client` | `string` |  | Provider name for identity_assertion, otherwise agent (self-described) or anonymous agent. |
| `organization_id` | `string` |  | Organization the registration is bound to (after a claim, or right away for identity_assertion). |
| `inbox_id` | `string` |  | The sandbox inbox of an anonymous registration. |
| `email` | `string` |  | Verified email bound at claim or carried by the identity assertion. |
| `issuer` | `string` |  | Issuer of the identity assertion (identity_assertion). |
| `agent_platform` | `string` |  | agent_platform claim of the identity assertion. |
| `created_at` | `string (date-time)` | required | When the agent registered. |
| `claimed_at` | `string (date-time)` |  | When a human claimed the registration. |
| `revoked_at` | `string (date-time)` |  | When the registration was revoked. |
| `expires_at` | `string (date-time)` |  | When an unclaimed registration expires. |

## ApiKeyPermissions

Permissions of the key, as `{ "<name>": true }`. Only names set to `true` are granted; `false` is the same as leaving the name out. An omitted (or `null`) permission object means full access for the key scope, like `{"*": true}`; neither includes `inbound_raw`, which must be granted by name. A key can only be given permissions its creator has. Another name set to `true` (for example AgentMail’s `calendar_*`, `app_*` or `account_update`, which MachineMail does not support) is a `400`.

| Field | Type | | Description |
| --- | --- | --- | --- |
| `*` | `boolean` |  | Every permission except inbound_raw. {"*": true, "inbound_raw": true} is full access plus raw injection. |
| `inbox_read` | `boolean` |  | Read inbox details. |
| `inbox_create` | `boolean` |  | Create new inboxes. |
| `inbox_update` | `boolean` |  | Update inbox settings. |
| `inbox_delete` | `boolean` |  | Delete inboxes. |
| `thread_read` | `boolean` |  | Read threads. |
| `thread_delete` | `boolean` |  | Delete threads. |
| `message_read` | `boolean` |  | Read messages. |
| `message_reply` | `boolean` |  | Reply and reply-all. message_send allows them too. |
| `message_send` | `boolean` |  | Send messages (and reply, reply-all, forward). |
| `message_update` | `boolean` |  | Update message labels. Also required to update threads. |
| `message_delete` | `boolean` |  | Delete messages. |
| `label_spam_read` | `boolean` |  | Access messages labeled spam. |
| `label_blocked_read` | `boolean` |  | Access messages labeled blocked. |
| `label_unauthenticated_read` | `boolean` |  | Access messages labeled unauthenticated. |
| `label_trash_read` | `boolean` |  | Access messages labeled trash. |
| `draft_read` | `boolean` |  | Read drafts. |
| `draft_create` | `boolean` |  | Create drafts. |
| `draft_update` | `boolean` |  | Update drafts. |
| `draft_delete` | `boolean` |  | Delete drafts. |
| `draft_send` | `boolean` |  | Send drafts. |
| `webhook_read` | `boolean` |  | Read webhook configurations. |
| `webhook_create` | `boolean` |  | Create webhooks. |
| `webhook_update` | `boolean` |  | Update webhooks. |
| `webhook_delete` | `boolean` |  | Delete webhooks. |
| `domain_read` | `boolean` |  | Read domain details. |
| `domain_create` | `boolean` |  | Create domains. |
| `domain_update` | `boolean` |  | Update domains. |
| `domain_delete` | `boolean` |  | Delete domains. |
| `list_entry_read` | `boolean` |  | Read list entries and suppressions. |
| `list_entry_create` | `boolean` |  | Create list entries and suppressions. |
| `list_entry_delete` | `boolean` |  | Delete list entries and suppressions. |
| `trusted_domain_read` | `boolean` |  | Read trusted domains and preview new ones. |
| `trusted_domain_create` | `boolean` |  | Add trusted domains. Organization API keys and the dashboard only: pod and inbox keys get 403 even with it. |
| `trusted_domain_update` | `boolean` |  | Change trusted domains. Organization API keys and the dashboard only, as for trusted_domain_create. |
| `trusted_domain_delete` | `boolean` |  | Remove trusted domains. |
| `metrics_read` | `boolean` |  | Read metrics and events. |
| `api_key_read` | `boolean` |  | Read API keys. |
| `api_key_create` | `boolean` |  | Create API keys. |
| `api_key_update` | `boolean` |  | Update API keys. |
| `api_key_delete` | `boolean` |  | Delete API keys. |
| `pod_read` | `boolean` |  | Read pods. |
| `pod_create` | `boolean` |  | Create pods. |
| `pod_update` | `boolean` |  | Update pods. |
| `pod_delete` | `boolean` |  | Delete pods. |
| `inbound_raw` | `boolean` |  | Inject raw mail with POST /inbound/raw. Never implied by an unrestricted key or *: it must be granted by name. |

## Attachment

| Field | Type | | Description |
| --- | --- | --- | --- |
| `attachment_id` | `string` | required | ID of the attachment. |
| `filename` | `string` |  | Filename of the attachment. |
| `size` | `integer` | required | Size of the attachment in bytes. |
| `content_type` | `string` |  | Content type of the attachment. |
| `content_disposition` | `string` |  | Content disposition of the attachment (inline or attachment). |
| `content_id` | `string` |  | Content ID of the attachment. |

## AttachmentResponse

| Field | Type | | Description |
| --- | --- | --- | --- |
| `attachment_id` | `string` | required | ID of the attachment. |
| `filename` | `string` |  | Filename of the attachment. |
| `size` | `integer` | required | Size of the attachment in bytes. |
| `content_type` | `string` |  | Content type of the attachment. |
| `content_disposition` | `string` |  | Content disposition of the attachment (inline or attachment). |
| `content_id` | `string` |  | Content ID of the attachment. |
| `download_url` | `string` | required | URL of the attachment bytes. Depending on the storage backend: a signed storage URL, the API URL GET .../attachments/{attachment_id}/download (send your API key), or a data: URI. |
| `expires_at` | `string (date-time)` | required | When download_url expires: one hour from the request unless the signed URL sets its own expiry. |

## AuthConfigResponse

How people sign in to MachineMail.

| Field | Type | | Description |
| --- | --- | --- | --- |
| `workos` | `boolean` | required | Legacy alias of email_code, kept for older clients: sign in with an email code (POST /auth/email/start), a social provider (GET /auth/social/{provider}) or SSO. |
| `email_code` | `boolean` | required | Email-code sign-in (POST /auth/email/start, then POST /auth/email/verify) is available. |
| `providers` | `("google" \| "github" \| "apple" \| "microsoft")[]` | required | Social providers on offer (GET /auth/social/{provider}). Empty when none are offered. |
| `magic_link` | `boolean` | required | Magic-link sign-in (POST /auth/magic-link) is available. |
| `password` | `boolean` | required | The deprecated password endpoints are available. |
| `authkit` | `boolean` | required, deprecated | Deprecated: always false. Kept for older clients; see email_code. |
| `passkey` | `boolean` | required | Passkey sign-in (POST /auth/passkey/options, then POST /auth/passkey/verify) is offered: show “Sign in with a passkey” and passkey autofill on the email field. |

## AuthorizationServerMetadata

RFC 8414 Authorization Server Metadata (as the MCP authorization specification asks for it) plus the auth.md `agent_auth` block.

| Field | Type | | Description |
| --- | --- | --- | --- |
| `resource` | `string` | required | The protected resource: <issuer>/ (the REST API and MCP), or <issuer>/<path> for a path-suffixed document. On the MCP server’s own host, the MCP server’s URL (https://mcp.machinemail.dev/mcp); on the issuer, <issuer>/mcp is the legacy MCP alias. |
| `authorization_servers` | `string[]` | required |  |
| `scopes_supported` | `("inboxes.read" \| "inboxes.write" \| "messages.read" \| "messages.send" \| "messages.write" \| "webhooks.read" \| "webhooks.write" \| "domains.read" \| "domains.write" \| "lists.read" \| "lists.write" \| "metrics.read" \| "pods.read" \| "pods.write" \| "api_keys.read" \| "api_keys.write")[]` | required |  |
| `bearer_methods_supported` | `string[]` | required | header. |
| `issuer` | `string` | required |  |
| `authorization_endpoint` | `string` | required | <issuer>/oauth2/authorize: authorization code grant with PKCE (S256). |
| `token_endpoint` | `string` | required |  |
| `registration_endpoint` | `string` | required | <issuer>/oauth2/register: Dynamic Client Registration (RFC 7591), public or confidential clients. |
| `revocation_endpoint` | `string` | required |  |
| `device_authorization_endpoint` | `string` | required | <issuer>/auth/cli/device/code: the device grant of the MachineMail CLI (client_id machinemail-cli only). |
| `jwks_uri` | `string` | required |  |
| `service_documentation` | `string` | required |  |
| `grant_types_supported` | `string[]` | required | authorization_code, refresh_token, urn:ietf:params:oauth:grant-type:device_code, urn:ietf:params:oauth:grant-type:jwt-bearer, urn:workos:agent-auth:grant-type:claim. |
| `response_types_supported` | `string[]` | required | code. |
| `response_modes_supported` | `string[]` | required | query. |
| `code_challenge_methods_supported` | `string[]` | required | S256. |
| `token_endpoint_auth_methods_supported` | `string[]` | required | none (public clients: every URL client ID, and registrations without a secret), client_secret_post and client_secret_basic (registrations that asked for a secret). Every client uses PKCE. |
| `revocation_endpoint_auth_methods_supported` | `string[]` | required | Same as token_endpoint_auth_methods_supported. |
| `client_id_metadata_document_supported` | `boolean` | required | true: an https URL is accepted as client_id (OAuth Client ID Metadata Document). |
| `authorization_response_iss_parameter_supported` | `boolean` | required | true: authorization responses carry iss (RFC 9207). |
| `agent_auth` | `object` | required |  |

## BatchGetMessagesRequest

| Field | Type | | Description |
| --- | --- | --- | --- |
| `message_ids` | `string[]` | required | IDs of the messages to fetch. Only the first 100 distinct IDs are read. |

## BatchGetMessagesResponse

| Field | Type | | Description |
| --- | --- | --- | --- |
| `limit` | `integer` | required | Number of IDs in the request. |
| `count` | `integer` | required | Number of messages returned. |
| `messages` | `Message[]` | required | Found messages, in request order. IDs not found in the inbox are omitted. |

## BatchUpdateMessagesRequest

| Field | Type | | Description |
| --- | --- | --- | --- |
| `message_ids` | `string[]` | required | IDs of the messages to update. Only the first 100 distinct IDs are updated. |
| `add_labels` | `string \| string[]` |  | Label or labels to add to every message. |
| `remove_labels` | `string \| string[]` |  | Label or labels to remove from every message. |

## BatchUpdateMessagesResponse

| Field | Type | | Description |
| --- | --- | --- | --- |
| `limit` | `integer` | required | Number of IDs in the request. |
| `count` | `integer` | required | Number of messages updated. |
| `updates` | `UpdateMessageResponse[]` | required | Updated messages with their new labels, in request order. IDs not found are omitted. |

## BearerApiKey

An API key presented as a bearer token in the `Authorization` header.

| Field | Type | | Description |
| --- | --- | --- | --- |
| `type` | `"bearer"` | required | Always bearer: MachineMail has no public-key credentials. |
| `api_key_id` | `string` | required | ID of the API key. |
| `prefix` | `string` | required | First 12 characters of the key (mm_live_ and 4 more), safe to display. |
| `name` | `string` | required | Name of the key. |
| `pod_id` | `string` |  | Pod the key is scoped to: it can only access resources in this pod. |
| `inbox_id` | `string` |  | Inbox the key is scoped to: it can only access this inbox. |
| `permissions` | `ApiKeyPermissions` |  | Permissions of the key, as { "<name>": true }. Only names set to true are granted; false is the same as leaving the name out. An omitted (or null) permission object means full access for the key scope, like {"*": true}; neither includes inbound_raw, which must be granted by name. A key can only be given permissions its creator has. Another name set to true (for example AgentMail’s calendar_*, app_* or account_update, which MachineMail does not support) is a 400. |
| `created_at` | `string (date-time)` | required | Time at which the key was created. |
| `expires_at` | `string (date-time)` |  | Time at which the key expires: requests with it then get 401. Absent when it does not expire. |
| `organization_id` | `string` | required | ID of the organization. |
| `used_at` | `string (date-time)` |  | Time at which the key was last used. |
| `revoked_at` | `string (date-time)` |  | When the key was deleted (revoked). Revoked keys are not listed but can still be fetched by ID. |
| `updated_at` | `string (date-time)` | required | Time at which the name, permissions or revocation last changed. Use does not change it. |

## Bounce

| Field | Type | | Description |
| --- | --- | --- | --- |
| `inbox_id` | `string` | required | ID of the inbox. |
| `thread_id` | `string` | required | ID of the thread. |
| `message_id` | `string` | required | ID of the message. |
| `timestamp` | `string (date-time)` | required | When it happened. |
| `type` | `string` | required | Bounce type: Permanent or Transient (or the provider type). |
| `sub_type` | `string` | required | Bounce sub-type, General when the provider gives none. |
| `recipients` | `Recipient[]` | required | Bounced recipients. Permanent bounces are added to the suppression list. |

## ClaimBlock

RFC 8628-shaped claim ceremony block.

| Field | Type | | Description |
| --- | --- | --- | --- |
| `user_code` | `string` | required | 6-digit code the human enters in the dashboard. |
| `verification_uri` | `string` | required | https://machinemail.dev/claim?claim_attempt_token=cat_...: the page to send the human to. |
| `expires_in` | `integer` | required | Seconds until user_code expires. |
| `interval` | `integer` | required | Minimum seconds between /oauth2/token polls. |

## ClaimCompleteRequest

| Field | Type | | Description |
| --- | --- | --- | --- |
| `user_code` | `string` | required | The 6-digit code the agent shows; spaces and dashes are ignored. |
| `claim_attempt_token` | `string` |  | The cat_... token from verification_uri; when given, it must belong to the same attempt as user_code. |

## ClaimCompleteResponse

The claimed registration plus the moved inbox.

| Field | Type | | Description |
| --- | --- | --- | --- |
| `agent_id` | `string` | required | ID of the registration (reg_...). |
| `registration_id` | `string` | required | Same value as agent_id. |
| `registration_type` | `"anonymous" \| "service_auth" \| "identity_assertion"` | required | anonymous: no human yet; a sandbox inbox until a human claims it. service_auth: on behalf of a named user, who confirms first. identity_assertion: an ID-JAG from a trusted agent provider. |
| `status` | `"claimed"` | required |  |
| `name` | `string` |  | Agent-described label. Untrusted. |
| `client` | `string` |  | Provider name for identity_assertion, otherwise agent (self-described) or anonymous agent. |
| `organization_id` | `string` | required | The organization of the signed-in user. |
| `inbox_id` | `string` |  | The sandbox inbox of an anonymous registration. |
| `email` | `string` |  | Verified email bound at claim or carried by the identity assertion. |
| `issuer` | `string` |  | Issuer of the identity assertion (identity_assertion). |
| `agent_platform` | `string` |  | agent_platform claim of the identity assertion. |
| `created_at` | `string (date-time)` | required | When the agent registered. |
| `claimed_at` | `string (date-time)` |  | When a human claimed the registration. |
| `revoked_at` | `string (date-time)` |  | When the registration was revoked. |
| `expires_at` | `string (date-time)` |  | When an unclaimed registration expires. |
| `inbox` | `object` |  | The former sandbox inbox, now permanent and in the user’s default pod. |

## ClaimPreviewResponse

An agent registration plus the claim details.

| Field | Type | | Description |
| --- | --- | --- | --- |
| `agent_id` | `string` | required | ID of the registration (reg_...). |
| `registration_id` | `string` | required | Same value as agent_id. |
| `registration_type` | `"anonymous" \| "service_auth" \| "identity_assertion"` | required | anonymous: no human yet; a sandbox inbox until a human claims it. service_auth: on behalf of a named user, who confirms first. identity_assertion: an ID-JAG from a trusted agent provider. |
| `status` | `"pending_claim" \| "claimed" \| "revoked"` | required |  |
| `name` | `string` |  | Agent-described label. Untrusted. |
| `client` | `string` |  | Provider name for identity_assertion, otherwise agent (self-described) or anonymous agent. |
| `organization_id` | `string` |  | Organization the registration is bound to (after a claim, or right away for identity_assertion). |
| `inbox_id` | `string` |  | The sandbox inbox of an anonymous registration. |
| `email` | `string` |  | Verified email bound at claim or carried by the identity assertion. |
| `issuer` | `string` |  | Issuer of the identity assertion (identity_assertion). |
| `agent_platform` | `string` |  | agent_platform claim of the identity assertion. |
| `created_at` | `string (date-time)` | required | When the agent registered. |
| `claimed_at` | `string (date-time)` |  | When a human claimed the registration. |
| `revoked_at` | `string (date-time)` |  | When the registration was revoked. |
| `expires_at` | `string (date-time)` |  | When an unclaimed registration expires. |
| `claim_email` | `string` |  | Only this user may claim the registration. |
| `user_code_expires_at` | `string (date-time)` |  | When the code expires. |
| `sandbox_inbox` | `object` |  | The sandbox inbox of an anonymous registration. |

## ClaimStartRequest

Send `claim_token` (and optionally `email`), or `agent_id` with Bearer proof.

| Field | Type | | Description |
| --- | --- | --- | --- |
| `claim_token` | `string` |  | Protocol form: the claim_token from registration. |
| `email` | `string` |  | Binds the claim to this human. For service_auth and identity_assertion it must match the bound email. |
| `agent_id` | `string` |  | Alternative to claim_token; needs the registration’s access token or identity assertion as Authorization: Bearer. |
| `registration_id` | `string` |  | Alias of agent_id. |

## ClaimStartResponse

The `claim_attempt` block and the flat `user_code`, `verification_uri`, `expires_in` and `interval` carry the same values.

| Field | Type | | Description |
| --- | --- | --- | --- |
| `registration_id` | `string` | required |  |
| `agent_id` | `string` | required |  |
| `claim_attempt_id` | `string` | required |  |
| `status` | `"initiated"` | required |  |
| `expires_at` | `string (date-time)` | required | When this attempt expires. |
| `claim_attempt` | `ClaimBlock` | required | RFC 8628-shaped claim ceremony block. |
| `user_code` | `string` | required | 6-digit code the human enters in the dashboard. |
| `verification_uri` | `string` | required | https://machinemail.dev/claim?claim_attempt_token=cat_...: the page to send the human to. |
| `expires_in` | `integer` | required | Seconds until user_code expires. |
| `interval` | `integer` | required | Minimum seconds between /oauth2/token polls. |

## CliDeviceApproveRequest

| Field | Type | | Description |
| --- | --- | --- | --- |
| `user_code` | `string` | required | The code the CLI printed (XXXX-XXXX); case, spaces and dashes are ignored. |
| `organization_id` | `string` |  | The organization the key is for, as the page showed it: it must be the one the session is signed in to (409 conflict otherwise, for example after another tab switched organizations). To create the key in another organization the person belongs to, switch the session to it first (POST /auth/organizations/switch). Default: the session’s organization. |

## CliDeviceAuthorization

A pending CLI sign-in, as the approval page shows it. Never includes the device code.

| Field | Type | | Description |
| --- | --- | --- | --- |
| `user_code` | `string` | required | The code, formatted XXXX-XXXX. |
| `device_id` | `string` | required | Opaque ID of this sign-in (cld_...), not a secret: the device_id of the session.cli.device stream events that report it approved, denied or redeemed. |
| `client_id` | `string` | required | Always machinemail-cli. |
| `client_name` | `string \| null` | required | What the CLI says it is. Self-reported. |
| `hostname` | `string \| null` | required | The machine the CLI says it runs on. Self-reported. |
| `key_name` | `string` | required | Name of the key approval creates: CLI on <hostname>. |
| `permissions` | `string[]` | required | Permissions of that key: ["*"] (every permission except inbound_raw). |
| `created_at` | `string (date-time)` | required | When the CLI asked for the code. |
| `expires_at` | `string (date-time)` | required | When the code expires. |

## CliDeviceCodeRequest

| Field | Type | | Description |
| --- | --- | --- | --- |
| `client_id` | `string` | required | Always machinemail-cli. |
| `client_name` | `string` |  | Shown to the human on the approval page, for example MachineMail CLI 0.4.0. At most 100 characters. (max length 100) |
| `hostname` | `string` |  | The machine the CLI runs on. Names the key (CLI on <hostname>) and is shown on the approval page. At most 253 characters. (max length 253) |

## CliDeviceCodeResponse

RFC 8628 device authorization response.

| Field | Type | | Description |
| --- | --- | --- | --- |
| `device_code` | `string` | required | Secret the CLI polls the token endpoint with. Never show it. |
| `user_code` | `string` | required | Code the human checks on the approval page: 8 letters as XXXX-XXXX (consonants only, no look-alikes). |
| `verification_uri` | `string` | required | https://machinemail.dev/cli: where the human approves. |
| `verification_uri_complete` | `string` | required | verification_uri with ?code=<user_code>, for opening in a browser. |
| `expires_in` | `integer` | required | Seconds until both codes expire (600). |
| `interval` | `integer` | required | Minimum seconds between polls of the token endpoint (5). |

## CliDeviceDecision

| Field | Type | | Description |
| --- | --- | --- | --- |
| `status` | `"approved" \| "denied"` | required |  |
| `key_name` | `string` | required | Name of the key the CLI receives (approved) or would have received (denied). |

## CliDeviceDecisionRequest

| Field | Type | | Description |
| --- | --- | --- | --- |
| `user_code` | `string` | required | The code the CLI printed (XXXX-XXXX); case, spaces and dashes are ignored. |

## CliDeviceTokenRequest

| Field | Type | | Description |
| --- | --- | --- | --- |
| `grant_type` | `string` | required | urn:ietf:params:oauth:grant-type:device_code. |
| `device_code` | `string` | required | The device_code from the device authorization response. |
| `client_id` | `string` | required | Always machinemail-cli. |

## CliDeviceTokenResponse

An organization API key with every permission except `inbound_raw`, minted for the approving user.

| Field | Type | | Description |
| --- | --- | --- | --- |
| `access_token` | `string` | required | The new API key (mm_live_...). Returned once; send it as Authorization: Bearer. |
| `token_type` | `"Bearer"` | required |  |
| `api_key_id` | `string` | required | ID of the key, for DELETE /v0/api-keys/{api_key_id} on logout. |
| `key_name` | `string` | required | Name of the key: CLI on <hostname>. |
| `organization` | `Organization` | required | The organization the key belongs to. |
| `user` | `User` | required | The person who approved the sign-in. |

## ClientRegistrationRequest

RFC 7591 client metadata. Unknown members are ignored.

| Field | Type | | Description |
| --- | --- | --- | --- |
| `redirect_uris` | `string[]` | required | Where the authorization response may be sent: https URLs, http loopback URLs (http://127.0.0.1, http://[::1] or http://localhost; any port is accepted at authorization time, RFC 8252), or a private-use scheme of a native app (cursor://...). No fragments. At most 10. (min items 1, max items 10) |
| `client_name` | `string` |  | Shown on the consent page, marked as unverified. At most 200 characters. (max length 200) |
| `client_uri` | `string` |  | Home page of the client; shown on the consent page when it is https. (max length 2000) |
| `logo_uri` | `string` |  | Logo shown on the consent page when it is https. (max length 2000) |
| `token_endpoint_auth_method` | `string` |  | none (the default: a public client, PKCE only), client_secret_post or client_secret_basic (a confidential client: the response carries a client_secret, and the token endpoint accepts it either way). Any other value is replaced with none in the response (RFC 7591 section 3.2.1). |
| `grant_types` | `string[]` |  | authorization_code and refresh_token (the default). Must include authorization_code (invalid_client_metadata otherwise); other values are dropped from the registration. |
| `response_types` | `string[]` |  | code (the default). Other values: invalid_client_metadata. |
| `scope` | `string` |  | Space-separated Scope values the client may ask for. Default: every scope. (max length 2000) |
| `application_type` | `"native" \| "web"` |  | native or web (OpenID Connect Dynamic Client Registration; required by MCP 2026-07-28). Default web. |
| `software_id` | `string` |  | Identifier of the client software. Stored, not verified. (max length 200) |
| `software_version` | `string` |  | Version of the client software. Stored, not verified. (max length 100) |

## ClientRegistrationResponse

RFC 7591 client information response. A public client has no `client_secret`.

| Field | Type | | Description |
| --- | --- | --- | --- |
| `client_id` | `string` | required | The new client identifier (oac_...). |
| `client_id_issued_at` | `integer` | required | Seconds since the epoch. |
| `client_name` | `string` |  |  |
| `client_uri` | `string` |  |  |
| `logo_uri` | `string` |  |  |
| `redirect_uris` | `string[]` | required |  |
| `grant_types` | `string[]` | required |  |
| `response_types` | `string[]` | required |  |
| `token_endpoint_auth_method` | `"none" \| "client_secret_post" \| "client_secret_basic"` | required | As requested when supported, otherwise none. |
| `client_secret` | `string` |  | Confidential clients only (client_secret_post, client_secret_basic): shown once, stored only as a hash. Keep it secret. |
| `client_secret_expires_at` | `integer` |  | With client_secret: 0, the secret does not expire. |
| `application_type` | `"native" \| "web"` | required |  |
| `scope` | `string` |  |  |
| `software_id` | `string` |  |  |
| `software_version` | `string` |  |  |

## Complaint

| Field | Type | | Description |
| --- | --- | --- | --- |
| `inbox_id` | `string` | required | ID of the inbox. |
| `thread_id` | `string` | required | ID of the thread. |
| `message_id` | `string` | required | ID of the message. |
| `timestamp` | `string (date-time)` | required | When it happened. |
| `type` | `string` | required | Complaint type, complaint when the provider gives none. |
| `sub_type` | `string` | required | Complaint sub-type, abuse when the provider gives none. |
| `recipients` | `Recipient[]` | required | Complaining recipients, as {address, status} objects (AgentMail documents plain strings). They are added to the suppression list. |

## CreateApiKeyResponse

| Field | Type | | Description |
| --- | --- | --- | --- |
| `api_key_id` | `string` | required | ID of the API key. |
| `prefix` | `string` | required | First 12 characters of the key (mm_live_ and 4 more), safe to display. |
| `name` | `string` | required | Name of the key. |
| `pod_id` | `string` |  | Pod the key is scoped to: it can only access resources in this pod. |
| `inbox_id` | `string` |  | Inbox the key is scoped to: it can only access this inbox. |
| `permissions` | `ApiKeyPermissions` |  | Permissions of the key, as { "<name>": true }. Only names set to true are granted; false is the same as leaving the name out. An omitted (or null) permission object means full access for the key scope, like {"*": true}; neither includes inbound_raw, which must be granted by name. A key can only be given permissions its creator has. Another name set to true (for example AgentMail’s calendar_*, app_* or account_update, which MachineMail does not support) is a 400. |
| `created_at` | `string (date-time)` | required | Time at which the key was created. |
| `expires_at` | `string (date-time)` |  | Time at which the key expires: requests with it then get 401. Absent when it does not expire. |
| `api_key` | `string` | required | The key itself (mm_live_ and 32 characters). Returned once; only a hash is stored. |

## CreateBearerApiKeyRequest

The route sets the scope: `/v0/api-keys` makes an organization key, `/v0/pods/{pod_id}/api-keys` a pod key, `/v0/inboxes/{inbox_id}/api-keys` an inbox key.

| Field | Type | | Description |
| --- | --- | --- | --- |
| `name` | `string` |  | Name of the key. Default API key. |
| `permissions` | `object \| null` |  | Permissions of the new key. Omitted or null: full access for the key scope (inbound_raw excepted). |
| `expires_at` | `string (date-time) \| null` |  | When the key stops working, as an RFC 3339 timestamp at least 5 minutes from now (400 otherwise). A date alone or a time without a zone is read as UTC. Omit it or send null for no expiry (a key created with an expiring API key gets that key’s expiry instead). A key created with another API key cannot outlive it: a later time is a 403. An inherited expiry can be under 5 minutes away. It cannot be changed later; create a new key instead. |

## CreateDomainRequest

| Field | Type | | Description |
| --- | --- | --- | --- |
| `domain` | `string` | required | The domain name, for example example.com. Cannot be the shared domain or a domain another organization registered. |
| `feedback_enabled` | `boolean` |  | Stored and returned; has no effect. Default false. |
| `inbound_enabled` | `boolean` |  | Receive email for this domain. Default true. false makes it send-only (the inbound MX record is not required). |
| `client_id` | `string` |  | Idempotency key: a create with a client_id already used in the organization returns that domain with 200, or 409 when that domain is outside the pod of the API key or route. |
| `pod_id` | `string` |  | Pod to create the domain in. Defaults to the credential pod or the organization default pod. A pod-scoped credential can only name its own pod. |
| `catch_all` | `boolean` |  | Create an inbox for any address at the domain that receives mail. Default false. |
| `catch_all_ttl_seconds` | `integer \| null` |  | Make inboxes created by catch-all ephemeral with this TTL. null or absent: permanent. |

## CreateDraftRequest

| Field | Type | | Description |
| --- | --- | --- | --- |
| `labels` | `string[]` |  | Labels of the draft. |
| `reply_to` | `string \| string[]` |  | Reply-to addresses: one address or a list. |
| `to` | `string \| string[]` |  | Recipient addresses: one address or a list. |
| `cc` | `string \| string[]` |  | CC addresses: one address or a list. |
| `bcc` | `string \| string[]` |  | BCC addresses: one address or a list. |
| `subject` | `string` |  | Subject. |
| `text` | `string` |  | Plain text body. |
| `html` | `string` |  | HTML body. |
| `attachments` | `SendAttachment[]` |  | Attachments, sent with the draft. Each needs content or url; a url is fetched when the draft is sent. |
| `in_reply_to` | `string` |  | ID of a message in this inbox to reply to: the sent message gets its In-Reply-To and References headers. |
| `send_at` | `string (date-time) \| null` |  | Schedule the draft: it is sent automatically at this time (send_status: scheduled). null or absent: not scheduled. |
| `client_id` | `string` |  | Idempotency key: a create with a client_id already used in the organization returns that draft with 200, or 409 when that draft is outside the pod or inbox of the API key or route. |

## CreatedTrustedDomain

The entry, with any warnings about mail that stays blocked.

| Field | Type | | Description |
| --- | --- | --- | --- |
| `trusted_domain_id` | `string` | required | ID of the entry (td_...). |
| `entry_type` | `"domain" \| "email"` | required | domain: every address at the domain (and its subdomains with include_subdomains). email: exactly one address. |
| `domain` | `string` | required | The domain, or for entry_type: "email" the address, lowercased, with the domain in its ASCII (punycode) form. |
| `domain_unicode` | `string` | required | The same, with the domain in its Unicode form. Equal to domain for ASCII names. |
| `include_subdomains` | `boolean` | required | Also trust mail signed by subdomains (billing.example.com for example.com). Always false for addresses. |
| `scope` | `"org" \| "pod" \| "inbox"` | required | Where an entry applies: the whole organization, one pod, or one inbox. |
| `pod_id` | `string \| null` | required | The pod, for pod- and inbox-scoped entries. |
| `inbox_id` | `string \| null` | required | The inbox, for inbox-scoped entries. |
| `reason` | `string \| null` | required | Why it was added, for people reading the list. Never included in a message’s trust report. |
| `expires_at` | `string (date-time) \| null` | required | When the entry stops applying. null: never. |
| `created_by` | `object \| null` | required | Who added the entry. |
| `match_count` | `integer` | required | Messages this entry has marked trusted. |
| `last_matched_at` | `string (date-time) \| null` | required | When this entry last marked a message trusted. null: never. |
| `created_at` | `string (date-time)` | required | When the entry was added. |
| `updated_at` | `string (date-time)` | required | When the entry last changed. |
| `warnings` | `Warning[]` | required | Conflicts with block entries or allow-only scopes. Empty when there are none. |
| `released` | `object \| null` | required | Create with apply_to_existing: true: the held messages of the last 30 days this entry released (each emits message.released). null otherwise, and on update. |

## CreateInboxRequest

| Field | Type | | Description |
| --- | --- | --- | --- |
| `username` | `string` |  | Local part of the address: a-z, 0-9 and . _ + -, 1 to 64 characters. Randomly generated if omitted. On the shared domain, a username equal to another organization's slug is reserved for that organization (409). |
| `domain` | `string` |  | Domain of the address: a domain verified by this organization. Defaults to the shared domain. |
| `display_name` | `string` |  | Display name: Display Name <username@domain.com>. |
| `client_id` | `string` |  | Idempotency key: a create with a client_id already used in the organization returns that inbox with 200, or 409 when that inbox is outside the pod of the API key or route. |
| `status` | `"active" \| "paused"` |  | Whether the inbox sends and receives mail. paused stops both: sends return 403 (Inbox <inbox_id> is paused), and incoming mail is not stored; a message.rejected event with reason inbox_paused is emitted instead. Mail already in the inbox, its drafts and its API keys are kept. Set active to resume. |
| `metadata` | `Record<string, string \| number \| boolean>` |  | Custom key-value pairs attached to the inbox. |
| `pod_id` | `string` |  | Pod to create the inbox in. Defaults to the credential pod or the organization default pod. |
| `ephemeral` | `boolean` |  | true without ttl_seconds creates an ephemeral inbox with a one hour TTL (3600 seconds). |
| `ttl_seconds` | `integer \| null` |  | Creates an ephemeral inbox that expires ttl_seconds after creation, extended by each inbound message. |
| `import_source` | `"agentmail"` |  | Marks an inbox copied from another provider by an importer (machinemail import agentmail sets agentmail). The only effect: its inbox.created event is not delivered to webhooks or MCP subscribers, so they are not notified about an inbox that already existed elsewhere. The event is still recorded (GET /v0/events, the event stream), marked as an import in its payload, so the event log shows every import. Everything else about the create is unchanged. |

## CreateInvitationRequest

| Field | Type | | Description |
| --- | --- | --- | --- |
| `email` | `string` | required | Address to invite. |
| `role` | `"owner" \| "admin" \| "member"` |  | Role on joining. Default member. Only owners can invite owners. |

## CreateListEntryRequest

| Field | Type | | Description |
| --- | --- | --- | --- |
| `entry` | `string` | required | Email address or domain to add. (min length 1) |
| `reason` | `string` |  | Reason for adding the entry. |

## CreatePodRequest

| Field | Type | | Description |
| --- | --- | --- | --- |
| `name` | `string` |  | Name of the pod. Default Untitled pod. |
| `client_id` | `string` |  | Idempotency key: a create with a client_id already used in the organization returns that pod with 200. |

## CreateSessionOrganizationRequest

| Field | Type | | Description |
| --- | --- | --- | --- |
| `name` | `string` | required | Name of the new organization. |

## CreateSuppressionRequest

| Field | Type | | Description |
| --- | --- | --- | --- |
| `email` | `string` | required | Address to suppress. |
| `detail` | `string` |  | Note stored as detail. |
| `reason` | `string` | deprecated | Old name of detail (it is stored as detail; the suppression reason is always manual). Ignored when detail is set. |

## CreateTrustedDomainRequest

| Field | Type | | Description |
| --- | --- | --- | --- |
| `domain` | `string` | required | A domain (example.com) or an exact address (invoices@example.com). Unicode names are accepted and stored in ASCII (punycode) form. (min length 1, max length 320) |
| `include_subdomains` | `boolean` |  | Also trust mail signed by subdomains. Default false. Not allowed for an address. |
| `reason` | `string` |  | Why you trust it, for people reading the list. Never included in a message’s trust report. (max length 500) |
| `expires_at` | `string (date-time)` |  | When the entry stops applying. Must be in the future. Omit for never. |
| `confirm` | `boolean` |  | Required (true) for platforms where anyone can write the content (github.com, google.com, slack.com and similar, or one under the domain with include_subdomains) and for one address on a group or marketplace relay (team@googlegroups.com): without it the answer is 422 confirmation_required. |
| `client_id` | `string` |  | Idempotency key: a create with a client_id already used in the organization returns that entry (200). |
| `apply_to_existing` | `boolean` |  | Also release mail held over the last 30 days that the new entry now trusts: mail held for sender or authentication-history reasons that carries the domain’s own DKIM signature. Never mail held for malware, instructions to agents or phishing, mail a person quarantined, or mail a block or allow-only scope stops. Released mail emits message.released, not message.received. Default false. |

## CreateWebhookRequest

| Field | Type | | Description |
| --- | --- | --- | --- |
| `url` | `string` | required | Endpoint URL: a public http(s) URL (private and loopback addresses are rejected, and re-checked before every delivery). |
| `event_types` | `("message.received" \| "message.received.spam" \| "message.received.quarantined" \| "message.received.unauthenticated" \| "message.received.blocked" \| "message.sent" \| "message.delivered" \| "message.bounced" \| "message.complained" \| "message.rejected" \| "domain.verified" \| "domain.updated" \| "inbox.created" \| "inbox.expired" \| "draft.sent" \| "webhook.test" \| "trusted_domain.created" \| "trusted_domain.updated" \| "trusted_domain.deleted" \| "message.released")[]` |  | Event types the webhook receives. Omit it or send an empty array to receive every event type except domain.updated, message.released and trusted_domain.*, which are sent only when listed. |
| `client_id` | `string` |  | Idempotency key: a create with a client_id already used in the organization returns that webhook with 200, or 409 when that webhook is outside the pod or inbox of the API key or route. |
| `inbox_ids` | `string[]` |  | Only deliver events of these inboxes. |
| `pod_ids` | `string[]` |  | Only deliver events of these pods. |
| `headers` | `Record<string, string>` |  | Custom HTTP headers sent with every delivery to this webhook (every attempt, retry, replay and test event), alongside the X-MachineMail-* headers, for example {"Authorization": "Bearer <token>"} for an endpoint that requires one. Values are write-only: they are stored encrypted and no response returns them; reads list the names (header_names). The headers are not part of the signed payload: X-MachineMail-Signature covers the body only. At most 10 headers. Names are HTTP tokens of up to 64 characters, compared without regard to case; values are printable ASCII (no line breaks or control characters) of up to 4096 characters, with surrounding spaces removed, and names plus values total at most 8192 bytes. Reserved names are refused with 400: Host, Content-Length, Content-Type, Content-Encoding, Transfer-Encoding, Connection, Upgrade, TE, Trailer, Keep-Alive, Expect, User-Agent, Accept-Encoding, and every name starting with Proxy-, Sec-, X-MachineMail-, Svix- or Webhook-. Authorization and Cookie are allowed. |

## DeleteOrganizationRequest

| Field | Type | | Description |
| --- | --- | --- | --- |
| `code` | `string` | required | The 6-digit code from the email. |

## DeleteOrganizationResponse

The organization is deleted.

| Field | Type | | Description |
| --- | --- | --- | --- |
| `ok` | `true` | required |  |
| `organization_id` | `string` | required | ID of the deleted organization. |

## Delivery

| Field | Type | | Description |
| --- | --- | --- | --- |
| `inbox_id` | `string` | required | ID of the inbox. |
| `thread_id` | `string` | required | ID of the thread. |
| `message_id` | `string` | required | ID of the message. |
| `timestamp` | `string (date-time)` | required | When it happened. |
| `recipients` | `string[]` | required | Delivered recipients. |

## DnsConflict

| Field | Type | | Description |
| --- | --- | --- | --- |
| `kind` | `"mx" \| "spf" \| "dmarc" \| "cname"` | required | mx: the name already has MX records for other mail servers. spf: the name already has an SPF record without the MachineMail include:, or has more than one SPF record. dmarc: the name has more than one DMARC record. cname: the name is a CNAME, so no other record can be published at it. |
| `type` | `"TXT" \| "CNAME" \| "MX"` | required | Type of the existing records. |
| `name` | `string` | required | Name (host) the existing records are at. |
| `found` | `string[]` | required | The existing records, as DNS returned them. MX records are <priority> <host>. |
| `provider` | `string` |  | mx only: the mail provider the existing MX records belong to (for example Google Workspace), when it is recognised. |
| `title` | `string` | required | One short line naming what conflicts, for example Existing MX records at Google Workspace. On a verified domain whose MX name also has another provider’s MX records it describes the current state: Mail for example.com is shared with Google Workspace. |
| `summary` | `string` | required | One short line with the consequence. |
| `why` | `string` | required | Why it matters, in a sentence or two. |
| `message` | `string` | required | What the existing records are and why they conflict, in one paragraph that ends with why. |
| `options` | `object[]` | required | The ways to resolve the conflict. One entry when there is a single thing to do; pick one when there are several. |
| `resolution` | `string` | required | What to do about it, in one paragraph: the label and detail of every option. |
| `suggested_value` | `string` |  | spf only: the existing SPF record with the MachineMail include: added. Publish it in place of the existing record. |

## Domain

| Field | Type | | Description |
| --- | --- | --- | --- |
| `pod_id` | `string` | required | ID of the pod the domain belongs to. |
| `domain_id` | `string` | required | ID of the domain. |
| `domain` | `string` | required | The domain name (for example example.com), lowercased. |
| `feedback_enabled` | `boolean` | required | Stored as given. MachineMail does not send bounce and complaint notifications to inboxes; use the message.bounced and message.complained events. |
| `inbound_enabled` | `boolean` | required | Receive email for this domain. false makes it send-only: the inbound MX record is not required. |
| `subdomains_enabled` | `boolean` | required | Always false: inboxes on subdomains are not supported. |
| `tracking_enabled` | `boolean` | required | Always false: open tracking is not supported. |
| `client_id` | `string` |  | Client ID given at creation (idempotency key). |
| `updated_at` | `string (date-time)` | required | Time at which the domain was last updated. |
| `created_at` | `string (date-time)` | required | Time at which the domain was created. |
| `status` | `"NOT_STARTED" \| "PENDING" \| "INVALID" \| "FAILED" \| "VERIFYING" \| "VERIFIED"` | required | NOT_STARTED until the first verify, then VERIFIED when every required record is valid, INVALID when a required record has the wrong value, otherwise PENDING. For domains whose email provider reports its own verdict (Amazon SES): VERIFYING while every required record checks out but the provider has not confirmed them yet, and FAILED when the provider gave up (reason says why). |
| `reason` | `string` |  | Why the domain is not verified yet: what to do next, or which records are missing or invalid. Absent once VERIFIED. |
| `records` | `VerificationRecord[]` | required | DNS records to publish. |
| `organization_id` | `string` | required | ID of the organization. |
| `catch_all` | `boolean` | required | Mail to any address at the domain without an inbox creates one (created_by: catch_all). |
| `catch_all_ttl_seconds` | `integer` |  | TTL of inboxes created by catch-all; absent means they are permanent. |
| `nameservers` | `string[]` |  | Authoritative nameservers, detected on create and verify. |
| `dns_provider` | `"cloudflare" \| "route53" \| "google" \| "vercel" \| "namecheap" \| "godaddy" \| "digitalocean" \| "hetzner" \| "porkbun" \| "other"` |  | DNS host the nameservers belong to. |
| `dns_provider_url` | `string` |  | Link to the DNS host console. |
| `dns_conflicts` | `DnsConflict[]` |  | Existing DNS records that conflict with records, each with what to do about it. Looked up when the domain is added and again on every check. They never stop the domain from being added. Empty when there are none; absent when the lookup did not complete. |
| `domain_connect` | `object` |  | One-click DNS setup (Domain Connect), from the last discovery. See Get Domain Connect Setup. |
| `verified_at` | `string (date-time)` |  | When the domain became VERIFIED. |
| `last_checked_at` | `string (date-time)` |  | When the DNS records were last checked. |

## DomainConnect

| Field | Type | | Description |
| --- | --- | --- | --- |
| `supported` | `boolean` | required | The zone DNS provider supports Domain Connect and has the MachineMail template. |
| `provider_name` | `string` |  | Display name of the DNS provider. |
| `apply_url` | `string` |  | Signed link that opens the provider with the outstanding records prefilled. Absent when nothing is left to add. |
| `provider_id` | `string` |  | Domain Connect provider ID. |
| `url_sync_ux` | `string` |  | Base URL of the provider synchronous flow. |
| `width` | `integer` |  | Suggested popup width, in pixels. |
| `height` | `integer` |  | Suggested popup height, in pixels. |
| `reason` | `"no_domain_connect" \| "provider_error" \| "no_sync_flow" \| "template_not_supported" \| "records_not_templatable" \| "already_configured" \| "not_configured"` |  | Why one-click setup is not available, or already_configured when nothing is left to add. |
| `checked_at` | `string (date-time)` | required | When discovery last ran. |

## DomainItem

| Field | Type | | Description |
| --- | --- | --- | --- |
| `pod_id` | `string` | required | ID of the pod the domain belongs to. |
| `domain_id` | `string` | required | ID of the domain. |
| `domain` | `string` | required | The domain name (for example example.com), lowercased. |
| `feedback_enabled` | `boolean` | required | Stored as given. MachineMail does not send bounce and complaint notifications to inboxes; use the message.bounced and message.complained events. |
| `inbound_enabled` | `boolean` | required | Receive email for this domain. false makes it send-only: the inbound MX record is not required. |
| `subdomains_enabled` | `boolean` | required | Always false: inboxes on subdomains are not supported. |
| `tracking_enabled` | `boolean` | required | Always false: open tracking is not supported. |
| `client_id` | `string` |  | Client ID given at creation (idempotency key). |
| `updated_at` | `string (date-time)` | required | Time at which the domain was last updated. |
| `created_at` | `string (date-time)` | required | Time at which the domain was created. |
| `status` | `"NOT_STARTED" \| "PENDING" \| "INVALID" \| "FAILED" \| "VERIFYING" \| "VERIFIED"` | required | Verification status, as on Domain. |
| `reason` | `string` |  | Why the domain is not verified yet, as on Domain. |
| `records` | `VerificationRecord[]` | required | DNS records to publish, as on Domain. |
| `organization_id` | `string` | required | ID of the organization. |
| `catch_all` | `boolean` | required | Mail to any address at the domain without an inbox creates one (created_by: catch_all). |
| `catch_all_ttl_seconds` | `integer` |  | TTL of inboxes created by catch-all; absent means they are permanent. |
| `nameservers` | `string[]` |  | Authoritative nameservers, detected on create and verify. |
| `dns_provider` | `"cloudflare" \| "route53" \| "google" \| "vercel" \| "namecheap" \| "godaddy" \| "digitalocean" \| "hetzner" \| "porkbun" \| "other"` |  | DNS host the nameservers belong to. |
| `dns_provider_url` | `string` |  | Link to the DNS host console. |
| `dns_conflicts` | `DnsConflict[]` |  | Existing DNS records that conflict with records, each with what to do about it. Looked up when the domain is added and again on every check. They never stop the domain from being added. Empty when there are none; absent when the lookup did not complete. |
| `domain_connect` | `object` |  | One-click DNS setup (Domain Connect), from the last discovery. See Get Domain Connect Setup. |
| `verified_at` | `string (date-time)` |  | When the domain became VERIFIED. |
| `last_checked_at` | `string (date-time)` |  | When the DNS records were last checked. |

## Draft

| Field | Type | | Description |
| --- | --- | --- | --- |
| `inbox_id` | `string` | required | ID of the inbox. |
| `draft_id` | `string` | required | ID of the draft. |
| `labels` | `string[]` | required | Labels of the draft. The sent message gets them. |
| `to` | `string[]` |  | Recipient addresses. |
| `cc` | `string[]` |  | CC addresses. |
| `bcc` | `string[]` |  | BCC addresses. |
| `subject` | `string` |  | Subject of the draft. |
| `preview` | `string` |  | Text preview of the draft. |
| `attachments` | `Attachment[]` |  | Attachments of the draft. size is 0 for attachments given by url until the draft is sent (the URL is fetched then). |
| `in_reply_to` | `string` |  | ID of the message being replied to. |
| `send_status` | `"scheduled" \| "sending" \| "failed"` |  | scheduled while waiting for send_at, sending while a sender holds it, failed when a scheduled send failed (it is not retried). Absent on drafts without send_at. |
| `send_at` | `string (date-time)` |  | Time at which the draft is sent automatically. |
| `updated_at` | `string (date-time)` | required | Time at which the draft was last updated. |
| `client_id` | `string` |  | Client ID given at creation (idempotency key). |
| `reply_to` | `string[]` |  | Reply-to addresses. |
| `text` | `string` |  | Plain text body. |
| `html` | `string` |  | HTML body. |
| `references` | `string[]` |  | Message IDs the draft references (set from in_reply_to). |
| `created_at` | `string (date-time)` | required | Time at which the draft was created. |
| `pod_id` | `string` | required | ID of the pod. |

## DraftItem

| Field | Type | | Description |
| --- | --- | --- | --- |
| `inbox_id` | `string` | required | ID of the inbox. |
| `draft_id` | `string` | required | ID of the draft. |
| `labels` | `string[]` | required | Labels of the draft. The sent message gets them. |
| `to` | `string[]` |  | Recipient addresses. |
| `cc` | `string[]` |  | CC addresses. |
| `bcc` | `string[]` |  | BCC addresses. |
| `subject` | `string` |  | Subject of the draft. |
| `preview` | `string` |  | Text preview of the draft. |
| `attachments` | `Attachment[]` |  | Attachments of the draft. size is 0 for attachments given by url until the draft is sent (the URL is fetched then). |
| `in_reply_to` | `string` |  | ID of the message being replied to. |
| `send_status` | `"scheduled" \| "sending" \| "failed"` |  | scheduled while waiting for send_at, sending while a sender holds it, failed when a scheduled send failed (it is not retried). Absent on drafts without send_at. |
| `send_at` | `string (date-time)` |  | Time at which the draft is sent automatically. |
| `updated_at` | `string (date-time)` | required | Time at which the draft was last updated. |
| `client_id` | `string` |  | Client ID given at creation. |
| `reply_to` | `string[]` |  | Reply-to addresses. |
| `text` | `string` |  | Plain text body. |
| `html` | `string` |  | HTML body. |
| `references` | `string[]` |  | Message IDs the draft references. |
| `created_at` | `string (date-time)` | required | Time at which the draft was created. |
| `pod_id` | `string` | required | ID of the pod. |

## EmailCodesRequest

| Field | Type | | Description |
| --- | --- | --- | --- |
| `enabled` | `boolean` | required | false turns email sign-in codes off; true back on. |

## EmailCodesResponse

| Field | Type | | Description |
| --- | --- | --- | --- |
| `email_codes` | `boolean` | required | Email sign-in codes work for this account now. |

## EmailCodeStartRequest

| Field | Type | | Description |
| --- | --- | --- | --- |
| `email` | `string` |  | Address to send the code to. Optional with invitation_token (the code goes to the invited address). |
| `next` | `string` |  | Same-origin path to land on after sign-in, for example /cli?code=ABCD-EFGH. Anything else is ignored. Default /app. |
| `invitation_token` | `string` |  | An invitation token (from /invite/{token}): signing in accepts the invitation. |

## EmailCodeStartResponse

| Field | Type | | Description |
| --- | --- | --- | --- |
| `ok` | `true` | required |  |
| `redirect_to` | `string` |  | Set when the address’s domain signs in with SSO: send the browser here (the organization’s identity provider). Otherwise a code was emailed: show the code screen. |

## EmailCodeVerifyRequest

| Field | Type | | Description |
| --- | --- | --- | --- |
| `code` | `string` | required | The 6-digit code from the email. Spaces and dashes are ignored. |

## EmailPreferencesRequest

| Field | Type | | Description |
| --- | --- | --- | --- |
| `product_emails` | `boolean` | required | false stops getting-started emails from MachineMail; true turns them back on. |

## EmailPreferencesResponse

| Field | Type | | Description |
| --- | --- | --- | --- |
| `product_emails` | `boolean` | required | The person gets getting-started emails. Sign-in codes, invitations and account and security emails always arrive. |
| `changed_at` | `string (date-time)` |  | When the setting last changed. Absent when it never did. |

## Event

An event in the replayable log. Webhooks, the SSE stream and the WebSocket deliver the event envelope instead (`type: "event"` with the `data` keys also at the top level).

| Field | Type | | Description |
| --- | --- | --- | --- |
| `event_id` | `string` | required | ID of the event (evt_...). Sorts by creation time. |
| `event_type` | `"message.received" \| "message.received.spam" \| "message.received.quarantined" \| "message.received.unauthenticated" \| "message.received.blocked" \| "message.sent" \| "message.delivered" \| "message.bounced" \| "message.complained" \| "message.rejected" \| "domain.verified" \| "domain.updated" \| "inbox.created" \| "inbox.expired" \| "draft.sent" \| "webhook.test" \| "trusted_domain.created" \| "trusted_domain.updated" \| "trusted_domain.deleted" \| "message.released" \| "registration.created" \| "token.issued" \| "agent.claimed" \| "agent.revoked" \| "member.invited" \| "member.role_changed" \| "member.removed"` | required | Type of event: an events_EventType, or an audit event for agent registrations and members (registration.created, token.issued, agent.claimed, agent.revoked, member.invited, member.role_changed, member.removed), which is logged but never delivered to webhooks or streams. |
| `organization_id` | `string` | required | ID of the organization. |
| `pod_id` | `string` |  | ID of the pod. Absent for organization-level events. |
| `inbox_id` | `string` |  | ID of the inbox. Absent for events without an inbox (for example domain.verified). |
| `data` | `Record<string, unknown>` | required | Event payload, as delivered to webhooks: message.* events (and message.released) carry message and thread, delivery events their send, delivery, bounce, complaint or reject object, inbox.* the inbox, domain.verified and domain.updated the domain, trusted_domain.* the trusted_domain. |
| `timestamp` | `string (date-time)` | required | When the event happened (same as created_at). |
| `created_at` | `string (date-time)` | required | When the event was stored. |

## Evidence

What MachineMail has seen of the domain, for warnings before trusting it. All `false`, `null` or 0 when the entry is not a domain or address.

| Field | Type | | Description |
| --- | --- | --- | --- |
| `dkim_seen` | `boolean` | required | Mail of the last 90 days at this level carried its own DKIM signature, verified by MachineMail. When false, trust applies only to mail it signs. |
| `first_contact` | `boolean` | required | No mail from it has reached this level in the last 90 days. |
| `lookalike_of` | `string \| null` | required | A well-known brand domain or an existing trusted entry this name imitates (confusable characters or a near match), else null. |
| `recent_auth_failures` | `integer` | required | Messages from it in the last 30 days whose DKIM or DMARC check failed, or that failed a trusted entry’s check. |

## FirstKeyResponse

| Field | Type | | Description |
| --- | --- | --- | --- |
| `api_key` | `OneTimeApiKey` | required | An API key with its plaintext secret, returned once. |

## HealthResponse

| Field | Type | | Description |
| --- | --- | --- | --- |
| `status` | `"ok"` | required |  |

## Identity

Identity and scope of the credential.

| Field | Type | | Description |
| --- | --- | --- | --- |
| `scope_type` | `"organization" \| "pod" \| "inbox"` | required | The scope tier the credential is bound to. |
| `scope_id` | `string` | required | ID of the most specific scope: inbox_id for an inbox credential, pod_id for a pod credential, otherwise organization_id. |
| `organization_id` | `string` | required | ID of the organization. |
| `pod_id` | `string` |  | ID of the pod the credential is scoped to. Present when scope_type is pod, and for inbox credentials created under a pod. |
| `inbox_id` | `string` |  | ID of the inbox the credential is scoped to. Present when scope_type is inbox. |
| `api_key_id` | `string` |  | ID of the API key used. Absent for dashboard sessions and agent access tokens. |
| `api_key_expires_at` | `string (date-time)` |  | When the API key used expires. Absent when it does not expire, and for other credentials. |
| `user` | `object` |  | The signed-in user, for a dashboard session. |

## InboundRawResponse

| Field | Type | | Description |
| --- | --- | --- | --- |
| `count` | `integer` | required | Number of messages stored (one per recipient inbox that accepted it). |
| `messages` | `Message[]` | required | The stored messages, marked received_via: "raw". |
| `errors` | `object[]` |  | Recipients that could not be delivered to, when there were several and at least one succeeded. |
| `held` | `object[]` |  | Recipients whose copy was held (not delivered yet) because the organization reached a Free plan limit: monthly, daily or storage, or queued behind mail already held. Held mail is delivered when the allowance resets or after an upgrade. |

## Inbox

| Field | Type | | Description |
| --- | --- | --- | --- |
| `pod_id` | `string` | required | ID of the pod the inbox belongs to. |
| `inbox_id` | `string` | required | ID of the inbox: its email address, as in AgentMail (inbox_id == email). |
| `email` | `string` | required | Email address of the inbox. |
| `display_name` | `string` |  | Display name: Display Name <username@domain.com>. |
| `client_id` | `string` |  | Client ID given at creation (idempotency key). |
| `status` | `"active" \| "paused"` | required | Whether the inbox sends and receives mail. paused stops both: sends return 403 (Inbox <inbox_id> is paused), and incoming mail is not stored; a message.rejected event with reason inbox_paused is emitted instead. Mail already in the inbox, its drafts and its API keys are kept. Set active to resume. |
| `metadata` | `Record<string, string \| number \| boolean>` |  | Custom key-value pairs attached to the inbox. |
| `updated_at` | `string (date-time)` | required | Time at which the inbox was last updated. |
| `created_at` | `string (date-time)` | required | Time at which the inbox was created. |
| `organization_id` | `string` | required | ID of the organization. |
| `ephemeral` | `boolean` | required | Whether the inbox expires (see ttl_seconds). |
| `ttl_seconds` | `integer` |  | TTL of an ephemeral inbox. Each inbound message pushes expires_at to now + ttl_seconds. |
| `expires_at` | `string (date-time)` |  | When an ephemeral inbox and its mail are deleted. |
| `created_by` | `"api" \| "catch_all" \| "dashboard" \| "agent"` | required | What created the inbox: the API, a catch-all domain, the dashboard or an agent registration. |

## InboxAvailability

| Field | Type | | Description |
| --- | --- | --- | --- |
| `available` | `boolean` | required |  |
| `email` | `string` | required | The address that was checked (lowercased). |
| `reason` | `"invalid_username" \| "domain_not_found" \| "taken"` |  | Why the address is not available. |
| `message` | `string` |  | Human-readable reason. |

## Invitation

An invitation to join the organization.

| Field | Type | | Description |
| --- | --- | --- | --- |
| `invitation_id` | `string` | required | ID of the invitation. |
| `organization_id` | `string` | required | ID of the organization the invitee joins. |
| `email` | `string` | required | Address the invitation was sent to. Only this address can accept it. |
| `role` | `"owner" \| "admin" \| "member"` | required | Role in the organization. Owners and admins invite, change roles and remove members; only owners grant or remove the owner role, and an organization always keeps one owner. |
| `state` | `"pending" \| "accepted" \| "expired" \| "revoked"` | required | State of the invitation. |
| `invited_by_user_id` | `string` |  | ID of the member who sent it, when known. |
| `expires_at` | `string (date-time)` | required | When the invitation stops working (7 days after it was sent). |
| `created_at` | `string (date-time)` | required | When the invitation was sent. |

## InvitationPreviewResponse

What an invitation link is for, before signing in.

| Field | Type | | Description |
| --- | --- | --- | --- |
| `organization_name` | `string` | required | The organization the invitation is to. |
| `email` | `string` | required | The invited address, masked (a••@example.com). |
| `state` | `"pending" \| "accepted" \| "expired" \| "revoked"` | required | Only a pending invitation can be accepted. |
| `expires_at` | `string (date-time)` | required | When the invitation expires. |

## JsonRpcError

JSON-RPC 2.0 error response.

| Field | Type | | Description |
| --- | --- | --- | --- |
| `jsonrpc` | `"2.0"` | required |  |
| `error` | `object` | required |  |
| `id` | `string \| number \| null` | required |  |

## JsonWebKeySet

RFC 7517 JWK Set.

| Field | Type | | Description |
| --- | --- | --- | --- |
| `keys` | `Record<string, unknown>[]` | required |  |

## ListAgentRegistrationsResponse

| Field | Type | | Description |
| --- | --- | --- | --- |
| `count` | `integer` | required | Number of items returned. |
| `limit` | `integer` |  | Maximum number of items per page. Default 50, maximum 500; larger values are capped. |
| `next_page_token` | `string` |  | Page token for pagination. Absent on the last page. |
| `agent_registrations` | `AgentRegistration[]` | required | Newest first unless ascending is set. |

## ListApiKeysResponse

| Field | Type | | Description |
| --- | --- | --- | --- |
| `count` | `integer` | required | Number of items returned. |
| `limit` | `integer` | required | Page size used. |
| `next_page_token` | `string` |  | Page token for pagination. Absent on the last page. |
| `api_keys` | `BearerApiKey[]` | required | Keys in the route scope that are not revoked, ordered by created_at, newest first unless ascending is set. |

## ListDeliveriesResponse

| Field | Type | | Description |
| --- | --- | --- | --- |
| `count` | `integer` | required | Number of items returned. |
| `limit` | `integer` |  | Maximum number of items per page. Default 50, maximum 500; larger values are capped. |
| `next_page_token` | `string` |  | Page token for pagination. Absent on the last page. |
| `deliveries` | `WebhookDelivery[]` | required | Ordered by created_at, newest first unless ascending is set. |

## ListDomainsResponse

| Field | Type | | Description |
| --- | --- | --- | --- |
| `count` | `integer` | required | Number of items returned. |
| `limit` | `integer` |  | Maximum number of items per page. Default 50, maximum 500; larger values are capped. |
| `next_page_token` | `string` |  | Page token for pagination. Absent on the last page. |
| `domains` | `DomainItem[]` | required | Ordered by created_at, newest first unless ascending is set. |

## ListDraftsResponse

| Field | Type | | Description |
| --- | --- | --- | --- |
| `count` | `integer` | required | Number of items returned. |
| `limit` | `integer` |  | Maximum number of items per page. Default 50, maximum 500; larger values are capped. |
| `next_page_token` | `string` |  | Page token for pagination. Absent on the last page. |
| `drafts` | `DraftItem[]` | required | Ordered by created_at, newest first unless ascending is set. |

## ListEntry

An allow or block list entry. The route sets the list: `/v0/lists` is the organization list, `/v0/pods/{pod_id}/lists` the pod list, `/v0/inboxes/{inbox_id}/lists` the inbox list.

| Field | Type | | Description |
| --- | --- | --- | --- |
| `entry` | `string` | required | Email address or domain (lowercased). |
| `organization_id` | `string` | required | ID of the organization. |
| `pod_id` | `string` |  | ID of the pod, for pod-scoped entries. |
| `inbox_id` | `string` |  | ID of the inbox, for inbox-scoped entries. |
| `reason` | `string` |  | Reason given when the entry was added. |
| `direction` | `"send" \| "receive" \| "reply"` | required | receive: who may send to you (inbound mail from a blocked sender is labeled BLOCKED). send: who you may send to (a blocked recipient is a 403). reply entries are stored and listed but not applied to any mail yet. |
| `list_type` | `"allow" \| "block"` | required | block refuses matching addresses. allow makes the list exclusive: once it has an entry, only matching addresses pass. |
| `entry_type` | `"email" \| "domain"` | required | Whether the entry is an email address or a domain. |
| `read_only` | `boolean` | required | Always false: every entry can be deleted through the API. |
| `created_at` | `string (date-time)` | required | Time at which the entry was created. |

## ListEventsResponse

| Field | Type | | Description |
| --- | --- | --- | --- |
| `count` | `integer` | required | Number of items returned. |
| `limit` | `integer` |  | Maximum number of items per page. Default 50, maximum 500; larger values are capped. |
| `next_page_token` | `string` |  | Page token for pagination. Absent on the last page. |
| `events` | `Event[]` | required | Newest first; oldest first when after is set or ascending is true. |

## ListInboxesResponse

| Field | Type | | Description |
| --- | --- | --- | --- |
| `count` | `integer` | required | Number of items returned. |
| `limit` | `integer` |  | Maximum number of items per page. Default 50, maximum 500; larger values are capped. |
| `next_page_token` | `string` |  | Page token for pagination. Absent on the last page. |
| `inboxes` | `Inbox[]` | required | Ordered by created_at, newest first unless ascending is set. |

## ListInvitationsResponse

| Field | Type | | Description |
| --- | --- | --- | --- |
| `count` | `integer` | required | Number of items returned. |
| `limit` | `integer` |  | Maximum number of items per page. Default 50, maximum 500; larger values are capped. |
| `next_page_token` | `string` |  | Page token for pagination. Absent on the last page. |
| `invitations` | `Invitation[]` | required | Pending invitations, newest first. |

## ListListEntriesResponse

| Field | Type | | Description |
| --- | --- | --- | --- |
| `count` | `integer` | required | Number of items returned. |
| `limit` | `integer` |  | Maximum number of items per page. Default 50, maximum 500; larger values are capped. |
| `next_page_token` | `string` |  | Page token for pagination. Absent on the last page. |
| `entries` | `ListEntry[]` | required | Ordered by entry, ascending unless ascending is false. |

## ListMembersResponse

| Field | Type | | Description |
| --- | --- | --- | --- |
| `count` | `integer` | required | Number of items returned. |
| `limit` | `integer` |  | Maximum number of items per page. Default 50, maximum 500; larger values are capped. |
| `next_page_token` | `string` |  | Page token for pagination. Absent on the last page. |
| `members` | `Member[]` | required | Oldest first unless ascending=false. |

## ListMessagesResponse

| Field | Type | | Description |
| --- | --- | --- | --- |
| `count` | `integer` | required | Number of items returned. |
| `limit` | `integer` |  | Maximum number of items per page. Default 50, maximum 500; larger values are capped. |
| `next_page_token` | `string` |  | Page token for pagination. Absent on the last page. |
| `messages` | `MessageItem[]` | required | Ordered by timestamp, newest first unless ascending is set. |

## ListOAuthGrantsResponse

| Field | Type | | Description |
| --- | --- | --- | --- |
| `count` | `integer` | required | Number of items returned. |
| `limit` | `integer` |  | Maximum number of items per page. Default 50, maximum 500; larger values are capped. |
| `next_page_token` | `string` |  | Page token for pagination. Absent on the last page. |
| `oauth_grants` | `OAuthGrant[]` | required | Newest first unless ascending is set. |

## ListPasskeysResponse

| Field | Type | | Description |
| --- | --- | --- | --- |
| `passkeys` | `Passkey[]` | required | The person’s passkeys, oldest first. |
| `email_codes` | `boolean` | required | Email sign-in codes work for this account. false once turned off (with at least one passkey). |
| `step_up` | `object` | required | Confirmation before sensitive changes. |
| `prompt` | `boolean` | required | Offer to create a passkey (no passkey yet, and the offer was not snoozed or declined). |

## ListPodsResponse

| Field | Type | | Description |
| --- | --- | --- | --- |
| `count` | `integer` | required | Number of items returned. |
| `limit` | `integer` |  | Maximum number of items per page. Default 50, maximum 500; larger values are capped. |
| `next_page_token` | `string` |  | Page token for pagination. Absent on the last page. |
| `pods` | `Pod[]` | required | Ordered by created_at, newest first unless ascending is set. A pod credential sees only its pod. |

## ListSessionOrganizationsResponse

| Field | Type | | Description |
| --- | --- | --- | --- |
| `organizations` | `MembershipOrganization[]` | required | Every organization the person belongs to, by name. |

## ListSuppressionsResponse

| Field | Type | | Description |
| --- | --- | --- | --- |
| `count` | `integer` | required | Number of items returned. |
| `limit` | `integer` |  | Maximum number of items per page. Default 50, maximum 500; larger values are capped. |
| `next_page_token` | `string` |  | Page token for pagination. Absent on the last page. |
| `suppressions` | `Suppression[]` | required | Newest first unless ascending is set. |

## ListThreadsResponse

| Field | Type | | Description |
| --- | --- | --- | --- |
| `count` | `integer` | required | Number of items returned. |
| `limit` | `integer` |  | Maximum number of items per page. Default 50, maximum 500; larger values are capped. |
| `next_page_token` | `string` |  | Page token for pagination. Absent on the last page. |
| `threads` | `ThreadItem[]` | required | Ordered by timestamp, newest first unless ascending is set. |
| `truncated` | `true` |  | Present when q matched more than 500 messages: only the threads of the newest 500 matching messages were considered. |

## ListTrustedDomainsResponse

| Field | Type | | Description |
| --- | --- | --- | --- |
| `count` | `integer` | required | Number of items returned. |
| `limit` | `integer` |  | Maximum number of items per page. Default 50, maximum 500; larger values are capped. |
| `next_page_token` | `string` |  | Page token for pagination. Absent on the last page. |
| `trusted_domains` | `TrustedDomain[]` | required | Entries stored at exactly this level (organization, pod or inbox), newest first unless ascending is set. |

## ListWebhooksResponse

| Field | Type | | Description |
| --- | --- | --- | --- |
| `count` | `integer` | required | Number of items returned. |
| `limit` | `integer` |  | Maximum number of items per page. Default 50, maximum 500; larger values are capped. |
| `next_page_token` | `string` |  | Page token for pagination. Absent on the last page. |
| `webhooks` | `Webhook[]` | required | Ordered by created_at, newest first unless ascending is set. |

## LoginRequest

| Field | Type | | Description |
| --- | --- | --- | --- |
| `email` | `string` | required |  |
| `password` | `string` | required |  |

## MagicLinkRequest

| Field | Type | | Description |
| --- | --- | --- | --- |
| `email` | `string` | required | Address to send the sign-in link to. |
| `org_name` | `string` |  | Organization name, used only when this is the address’s first sign-in. |
| `next` | `string` |  | Same-origin path to land on after sign-in, for example /claim?user_code=123456. Anything else is ignored. Default /app. |
| `organization_name` | `string` |  | Alias of org_name. |
| `organizationName` | `string` | deprecated | Alias of org_name. |

## MagicLinkResponse

| Field | Type | | Description |
| --- | --- | --- | --- |
| `ok` | `true` | required |  |

## McpDescription

How to connect to the remote MCP server.

| Field | Type | | Description |
| --- | --- | --- | --- |
| `name` | `string` | required |  |
| `description` | `string` | required | What the server is for, in one line. |
| `icons` | `object[]` | required | The MachineMail mark, for clients and registries that show an icon. |
| `version` | `string` | required |  |
| `transport` | `"streamable-http"` | required |  |
| `url` | `string` | required | The canonical MCP endpoint, https://mcp.machinemail.dev/mcp. https://machinemail.dev/mcp also works (legacy alias). |
| `authentication` | `object` | required |  |
| `docs` | `string` | required |  |

## Member

A person in the organization, with their role there and how they sign in.

| Field | Type | | Description |
| --- | --- | --- | --- |
| `user_id` | `string` | required | ID of the user. |
| `email` | `string` | required | Email address the user signs in with. |
| `name` | `string \| null` |  | Display name, when set. |
| `organization_id` | `string` | required | ID of the organization this record is about: the session’s active organization, or the organization listed. |
| `role` | `"owner" \| "admin" \| "member"` | required | Role in the organization. Owners and admins invite, change roles and remove members; only owners grant or remove the owner role, and an organization always keeps one owner. |
| `created_at` | `string (date-time)` | required | When the user was created. |
| `sign_in_methods` | `("email" \| "password" \| "google" \| "github" \| "apple" \| "microsoft" \| "sso" \| "passkey")[]` | required | Every method the member has used to sign in to this organization, in the order first used. Empty until their first sign-in after this was recorded. |
| `last_sign_in_method` | `("email" \| "password" \| "google" \| "github" \| "apple" \| "microsoft" \| "sso" \| "passkey" \| null) \| null` | required | How the member last signed in to this organization. null until their first sign-in after this was recorded. |

## MembershipOrganization

An organization the signed-in person belongs to.

| Field | Type | | Description |
| --- | --- | --- | --- |
| `organization_id` | `string` | required | ID of the organization. |
| `name` | `string` | required | Name of the organization. |
| `slug` | `string` | required | URL-safe name of the organization. Fixed when the organization is created; it never changes. |
| `role` | `"owner" \| "admin" \| "member"` | required | Role in the organization. Owners and admins invite, change roles and remove members; only owners grant or remove the owner role, and an organization always keeps one owner. |
| `current` | `boolean` | required | The session is signed in to this organization. |

## Message

| Field | Type | | Description |
| --- | --- | --- | --- |
| `inbox_id` | `string` | required | ID of the inbox. |
| `thread_id` | `string` | required | ID of the thread. |
| `message_id` | `string` | required | ID of the message. |
| `labels` | `string[]` | required | Labels of the message. |
| `timestamp` | `string (date-time)` | required | Time at which the message was sent or received. |
| `from` | `string` | required | Address of the sender. In format username@domain.com or Display Name <username@domain.com>. |
| `to` | `string[]` | required | Addresses of the recipients. In format username@domain.com or Display Name <username@domain.com>. |
| `cc` | `string[]` |  | Addresses of the CC recipients. In format username@domain.com or Display Name <username@domain.com>. |
| `bcc` | `string[]` |  | Addresses of the BCC recipients. In format username@domain.com or Display Name <username@domain.com>. |
| `subject` | `string` |  | Subject of the message. |
| `preview` | `string` |  | Text preview of the message. |
| `attachments` | `Attachment[]` |  | Attachments of the message. |
| `in_reply_to` | `string` |  | ID of the message being replied to. |
| `references` | `string[]` |  | IDs of previous messages in the thread. |
| `headers` | `Record<string, string>` |  | Headers of the message. |
| `authentication_results` | `MessageAuthenticationResults` |  | SPF, DKIM and DMARC verdicts for a received message, each with the domain it is about, as recorded when the message reached MachineMail: by the receiving mail provider (for example Amazon SES), or by the Authentication-Results header of the server that delivered it to MachineMail. A mechanism that was not evaluated is absent. The whole field is absent on sent messages, on messages injected through POST /inbound/raw (their headers are not believed), when nothing was evaluated, and on messages received before the field was introduced. As in AgentMail, only verdicts that speak for the sender are kept. spf is present only when spf_domain is aligned with the sender (shares an organizational domain with the From domain, or with the Sender domain when From spans several domains): an unaligned SPF result is withheld whatever its value. dkim and dmarc are withheld when From spans more than one domain or could not be parsed. Unlike the trust report (trust.authentication), which collapses each result to the values MachineMail scores on and adds aligned, these are the uncollapsed results: permerror and temperror stay as they are. dkim_domain is the signing domain as written: compare it with the From domain before treating a DKIM pass as proof of the sender, and use trust.level (or the UNAUTHENTICATED label) to decide whether to act on the message. Do not reject mail on dmarc: fail alone: forwarding breaks it. |
| `size` | `integer` | required | Size of the message in bytes. |
| `updated_at` | `string (date-time)` | required | Time at which the message was last updated. |
| `created_at` | `string (date-time)` | required | Time at which the message was created. |
| `pod_id` | `string` | required | ID of the pod. |
| `direction` | `"inbound" \| "outbound"` | required | Whether the message was received or sent. |
| `delivery_status` | `"sent" \| "delivered" \| "bounced" \| "complained"` |  | Outbound only: last delivery state reported by the transport. |
| `trust_level` | `"verified" \| "known" \| "unknown" \| "suspicious" \| "quarantined"` |  | Inbound only: trust.level, for list badges. |
| `reply_to` | `string[]` |  | Reply-to addresses. In format username@domain.com or Display Name <username@domain.com>. |
| `text` | `string` |  | Plain text body. Untrusted: agents should read trust.content.text or extracted_text. |
| `html` | `string` |  | HTML body. Untrusted. |
| `extracted_text` | `string` |  | New text content, without quoted replies. |
| `extracted_html` | `string` |  | New HTML content, without quoted replies. |
| `received_via` | `"raw"` |  | raw when the message was injected with POST /inbound/raw (authentication not verified). |
| `held_at` | `string (date-time)` |  | When the message arrived, for inbound mail that was held because the organization reached a Free plan limit. created_at is then when it was delivered (released). |
| `trust` | `TrustReport` |  | Trust report of an inbound message. |

## MessageAuthenticationResults

SPF, DKIM and DMARC verdicts for a received message, each with the domain it is about, as recorded when the message reached MachineMail: by the receiving mail provider (for example Amazon SES), or by the `Authentication-Results` header of the server that delivered it to MachineMail. A mechanism that was not evaluated is absent. The whole field is absent on sent messages, on messages injected through `POST /inbound/raw` (their headers are not believed), when nothing was evaluated, and on messages received before the field was introduced.

As in AgentMail, only verdicts that speak for the sender are kept. `spf` is present only when `spf_domain` is aligned with the sender (shares an organizational domain with the From domain, or with the Sender domain when From spans several domains): an unaligned SPF result is withheld whatever its value. `dkim` and `dmarc` are withheld when From spans more than one domain or could not be parsed.

Unlike the trust report (`trust.authentication`), which collapses each result to the values MachineMail scores on and adds `aligned`, these are the uncollapsed results: `permerror` and `temperror` stay as they are. `dkim_domain` is the signing domain as written: compare it with the From domain before treating a DKIM `pass` as proof of the sender, and use `trust.level` (or the UNAUTHENTICATED label) to decide whether to act on the message. Do not reject mail on `dmarc: fail` alone: forwarding breaks it.

| Field | Type | | Description |
| --- | --- | --- | --- |
| `spf` | `"pass" \| "fail" \| "none" \| "neutral" \| "softfail" \| "temperror" \| "permerror"` |  | Result of one authentication mechanism, in the vocabulary of the Authentication-Results header (RFC 8601). none: there was nothing to evaluate (no SPF record for the envelope sender domain, no DKIM signature, or no DMARC record for the From domain). temperror: the check could not complete, for example a DNS timeout. |
| `dkim` | `"pass" \| "fail" \| "none" \| "neutral" \| "softfail" \| "temperror" \| "permerror"` |  | Result of one authentication mechanism, in the vocabulary of the Authentication-Results header (RFC 8601). none: there was nothing to evaluate (no SPF record for the envelope sender domain, no DKIM signature, or no DMARC record for the From domain). temperror: the check could not complete, for example a DNS timeout. |
| `dmarc` | `"pass" \| "fail" \| "none" \| "neutral" \| "softfail" \| "temperror" \| "permerror"` |  | Result of one authentication mechanism, in the vocabulary of the Authentication-Results header (RFC 8601). none: there was nothing to evaluate (no SPF record for the envelope sender domain, no DKIM signature, or no DMARC record for the From domain). temperror: the check could not complete, for example a DNS timeout. |
| `spf_domain` | `string` |  | Envelope sender (MAIL FROM) domain the spf verdict is about (smtp.mailfrom, or the HELO name when the envelope sender was empty). |
| `dkim_domain` | `string` |  | Signing domain (d=) of the signature the dkim verdict is about: for pass, a signature that passed, preferring one aligned with the From domain. Present only when dkim is pass or fail. It is the domain as signed: compare it with the From domain before relying on it. |
| `dmarc_domain` | `string` |  | From domain the dmarc verdict is about (header.from). |
| `arc_sealer` | `string` |  | Domain of the forwarder whose ARC seal the verdicts were read from (for example google.com): they then describe the message as that forwarder received it. Present only on forwarded mail, and only beside at least one verdict. MachineMail does not validate ARC chains, so it reads verdicts from ARC only when its trust layer is set to believe any results header; by default it believes only its own receiving server and does not send this key. |

## MessageItem

A message without its bodies, as in lists.

| Field | Type | | Description |
| --- | --- | --- | --- |
| `inbox_id` | `string` | required | ID of the inbox. |
| `thread_id` | `string` | required | ID of the thread. |
| `message_id` | `string` | required | ID of the message. |
| `labels` | `string[]` | required | Labels of the message. |
| `timestamp` | `string (date-time)` | required | Time at which the message was sent or received. |
| `from` | `string` | required | Address of the sender. In format username@domain.com or Display Name <username@domain.com>. |
| `to` | `string[]` | required | Addresses of the recipients. In format username@domain.com or Display Name <username@domain.com>. |
| `cc` | `string[]` |  | Addresses of the CC recipients. In format username@domain.com or Display Name <username@domain.com>. |
| `bcc` | `string[]` |  | Addresses of the BCC recipients. In format username@domain.com or Display Name <username@domain.com>. |
| `subject` | `string` |  | Subject of the message. |
| `preview` | `string` |  | Text preview of the message. |
| `attachments` | `Attachment[]` |  | Attachments of the message. |
| `in_reply_to` | `string` |  | ID of the message being replied to. |
| `references` | `string[]` |  | IDs of previous messages in the thread. |
| `headers` | `Record<string, string>` |  | Headers of the message. |
| `authentication_results` | `MessageAuthenticationResults` |  | SPF, DKIM and DMARC verdicts for a received message, each with the domain it is about, as recorded when the message reached MachineMail: by the receiving mail provider (for example Amazon SES), or by the Authentication-Results header of the server that delivered it to MachineMail. A mechanism that was not evaluated is absent. The whole field is absent on sent messages, on messages injected through POST /inbound/raw (their headers are not believed), when nothing was evaluated, and on messages received before the field was introduced. As in AgentMail, only verdicts that speak for the sender are kept. spf is present only when spf_domain is aligned with the sender (shares an organizational domain with the From domain, or with the Sender domain when From spans several domains): an unaligned SPF result is withheld whatever its value. dkim and dmarc are withheld when From spans more than one domain or could not be parsed. Unlike the trust report (trust.authentication), which collapses each result to the values MachineMail scores on and adds aligned, these are the uncollapsed results: permerror and temperror stay as they are. dkim_domain is the signing domain as written: compare it with the From domain before treating a DKIM pass as proof of the sender, and use trust.level (or the UNAUTHENTICATED label) to decide whether to act on the message. Do not reject mail on dmarc: fail alone: forwarding breaks it. |
| `size` | `integer` | required | Size of the message in bytes. |
| `updated_at` | `string (date-time)` | required | Time at which the message was last updated. |
| `created_at` | `string (date-time)` | required | Time at which the message was created. |
| `pod_id` | `string` | required | ID of the pod. |
| `direction` | `"inbound" \| "outbound"` | required | Whether the message was received or sent. |
| `delivery_status` | `"sent" \| "delivered" \| "bounced" \| "complained"` |  | Outbound only: last delivery state reported by the transport. |
| `trust_level` | `"verified" \| "known" \| "unknown" \| "suspicious" \| "quarantined"` |  | Inbound only: trust.level, for list badges. |

## MetricBucket

| Field | Type | | Description |
| --- | --- | --- | --- |
| `timestamp` | `string (date-time)` | required | Start of the bucket. |
| `count` | `integer` | required | Number of events in the bucket. |

## OAuthApproveRequest

| Field | Type | | Description |
| --- | --- | --- | --- |
| `request_id` | `string` | required |  |
| `pod_id` | `string` |  | Limit the grant to one pod of the organization. Default: the whole organization. |
| `inbox_id` | `string` |  | Limit the grant to one inbox (and its pod). |
| `organization_id` | `string` |  | The organization the grant is for, as the consent page showed it: it must be the one the session is signed in to (409 conflict otherwise, for example after another tab switched organizations). To connect the app to another organization the person belongs to, switch the session to it first (POST /auth/organizations/switch). Default: the session’s organization. |

## OAuthAuthorizationRequest

A pending authorization request, as the consent page shows it.

| Field | Type | | Description |
| --- | --- | --- | --- |
| `request_id` | `string` | required |  |
| `client` | `OAuthClientInfo` | required |  |
| `redirect_uri` | `string` | required | Where the response goes. |
| `redirect_host` | `string` | required | Host (or scheme, for an app link) of redirect_uri, for the human to check. |
| `redirect_loopback` | `boolean` | required | The response goes to a program on this computer (http://127.0.0.1, localhost). |
| `resource` | `string` | required | What the token will be good for: the MCP server (https://mcp.machinemail.dev/mcp, or the legacy <issuer>/mcp) or <issuer>/ (the REST API and MCP). |
| `scopes` | `ScopeDescription[]` | required | What the client asked for. |
| `created_at` | `string (date-time)` | required |  |
| `expires_at` | `string (date-time)` | required | When the request expires (10 minutes after it was made). |

## OAuthClientInfo

| Field | Type | | Description |
| --- | --- | --- | --- |
| `client_id` | `string` | required |  |
| `client_name` | `string \| null` | required | Self-reported by the client. |
| `client_uri` | `string \| null` | required | Self-reported; https only. |
| `logo_uri` | `string \| null` | required | Self-reported; https only. |
| `registration` | `"dynamic" \| "metadata_document"` | required | dynamic: registered at /oauth2/register (name and logo unverified). metadata_document: client_id is the URL of its metadata document, so its host is verified. |
| `host` | `string \| null` | required | metadata_document: the host that serves the document (for example claude.ai). |

## OAuthDecision

| Field | Type | | Description |
| --- | --- | --- | --- |
| `status` | `"approved" \| "denied"` | required |  |
| `redirect_to` | `string` | required | Send the browser here: redirect_uri with code (or error=access_denied), state and iss. |

## OAuthDenyRequest

| Field | Type | | Description |
| --- | --- | --- | --- |
| `request_id` | `string` | required |  |

## OAuthError

OAuth / auth.md error body (RFC 6749 section 5.2, RFC 7591 section 3.2.2). The `/agent/*` and `/oauth2/*` endpoints use this shape instead of the API error envelope.

| Field | Type | | Description |
| --- | --- | --- | --- |
| `error` | `string` | required | Error code: invalid_request, invalid_grant, invalid_client, invalid_target, invalid_scope, invalid_client_metadata, invalid_redirect_uri, unsupported_grant_type, unsupported_response_type, access_denied, authorization_pending, slow_down, expired_token, interaction_required, login_required, invalid_claim_token, claimed_or_in_flight, claim_expired, rate_limited, anonymous_not_enabled, service_auth_not_enabled, identity_assertion_not_enabled, invalid_issuer, invalid_signature, invalid_audience, expired, replay_detected, invalid_client_id, missing_verified_email. |
| `error_description` | `string` | required |  |
| `message` | `string` | required | Same text as error_description. |
| `registration_id` | `string` |  | interaction_required only. |
| `agent_id` | `string` |  | interaction_required only. |
| `registration_type` | `"anonymous" \| "service_auth" \| "identity_assertion"` |  | interaction_required only. |
| `claim_url` | `string` |  | interaction_required only. |
| `claim_token` | `string` |  | interaction_required only. |
| `claim_token_expires` | `string (date-time)` |  | interaction_required only. |
| `post_claim_scopes` | `("inboxes.read" \| "inboxes.write" \| "messages.read" \| "messages.send" \| "messages.write" \| "webhooks.read" \| "webhooks.write" \| "domains.read" \| "domains.write" \| "lists.read" \| "lists.write" \| "metrics.read" \| "pods.read" \| "pods.write" \| "api_keys.read" \| "api_keys.write")[]` |  | interaction_required only. |
| `claim` | `object` |  | interaction_required only. |
| `max_age` | `integer` |  | login_required only: the maximum auth_time age in seconds. |

## OAuthGrant

An app a person connected with OAuth. Revoking it ends its access and refresh tokens at once.

| Field | Type | | Description |
| --- | --- | --- | --- |
| `grant_id` | `string` | required | ID of the grant (oag_...). |
| `client_id` | `string` | required |  |
| `client_name` | `string \| null` | required | Self-reported by the client at registration. |
| `client_uri` | `string \| null` | required |  |
| `logo_uri` | `string \| null` | required |  |
| `registration` | `"dynamic" \| "metadata_document"` | required |  |
| `user_id` | `string` | required | Who approved it. |
| `user_email` | `string \| null` | required |  |
| `scopes` | `("inboxes.read" \| "inboxes.write" \| "messages.read" \| "messages.send" \| "messages.write" \| "webhooks.read" \| "webhooks.write" \| "domains.read" \| "domains.write" \| "lists.read" \| "lists.write" \| "metrics.read" \| "pods.read" \| "pods.write" \| "api_keys.read" \| "api_keys.write")[]` | required |  |
| `resource` | `string` | required | Audience of its access tokens. |
| `pod_id` | `string \| null` | required | Set when the grant is limited to one pod. |
| `inbox_id` | `string \| null` | required | Set when the grant is limited to one inbox. |
| `created_at` | `string (date-time)` | required |  |
| `last_used_at` | `string (date-time) \| null` | required | Last token refresh or API call (updated at most once a minute). |
| `expires_at` | `string (date-time)` | required | When the current refresh token expires unless it is used (30 days after the last refresh). |

## OkResponse

Acknowledgement.

| Field | Type | | Description |
| --- | --- | --- | --- |
| `ok` | `true` | required |  |

## OneTimeApiKey

An API key with its plaintext secret, returned once.

| Field | Type | | Description |
| --- | --- | --- | --- |
| `api_key_id` | `string` | required | ID of the API key. |
| `api_key` | `string` | required | Plaintext secret (mm_live_...). Shown once. |
| `prefix` | `string` | required | First characters of the key, shown in listings. |
| `name` | `string` | required | Name of the key. |
| `created_at` | `string (date-time)` | required | When the key was created. |

## Organization

The organization of the credential. `plan` and `GET /v0/organizations/usage` report plan allowances; inboxes are unlimited (no `inbox_limit`) and there are no billing fields.

| Field | Type | | Description |
| --- | --- | --- | --- |
| `organization_id` | `string` | required | ID of the organization. |
| `name` | `string` | required | Name of the organization. |
| `slug` | `string` | required | URL-safe name of the organization, chosen from its name when it is created. It never changes, not even when the organization is renamed. {slug}@ on the shared domain is reserved for this organization: no other organization can create that inbox. |
| `plan` | `"free" \| "pro" \| "team" \| "scale"` | required | Plan of the organization. |
| `inbox_count` | `integer` | required | Number of inboxes in the credential scope. |
| `domain_count` | `integer` | required | Number of domains in the organization. |
| `domain_limit` | `integer` |  | Custom domains included in the plan. The Free plan cannot add more; paid plans can, and each extra domain is billed. Always present on GET /v0/organizations and PATCH /v0/organizations. |
| `usage` | `object` |  | Usage. Always present on GET /v0/organizations and PATCH /v0/organizations. |
| `shared_domain` | `string` |  | Domain every organization can create inboxes on, used when an inbox is created without domain. Always present on GET /v0/organizations and PATCH /v0/organizations. |
| `undo_send` | `boolean` | required | Undo send in the dashboard: when true (the default), a message sent from the MachineMail dashboard waits 5 seconds before it goes out, so it can be undone. Only the dashboard reads it: the API, the MCP server and the CLI always send at once. |
| `default_inbox_id` | `string \| null` | required | The default inbox of the organization (its inbox_id). A new organization gets {slug}@ on the shared domain, named after the organization, as its default inbox; when that address cannot be used (a reserved name), it starts without one. The dashboard lists the default inbox first. Change it with PATCH /v0/organizations. null when there is none: it was cleared, its inbox was deleted, or (for a pod or inbox credential) it is outside the credential scope. |
| `updated_at` | `string (date-time)` | required | Time at which the organization was last updated. |
| `created_at` | `string (date-time)` | required | Time at which the organization was created. |

## OrganizationDeletionStarted

A confirmation code was emailed to you.

| Field | Type | | Description |
| --- | --- | --- | --- |
| `email` | `string` | required | Where the code was sent: your own address, partly hidden. |
| `expires_at` | `string (date-time)` | required | When the code stops working (10 minutes after it was sent). |

## Passkey

A passkey the signed-in person can sign in with.

| Field | Type | | Description |
| --- | --- | --- | --- |
| `passkey_id` | `string` | required | ID of the passkey (pk_…). |
| `name` | `string` | required | Name shown in Settings (named after its password manager or device when added; up to 64 characters). |
| `created_at` | `string (date-time)` | required | When the passkey was added. |
| `last_used_at` | `string (date-time) \| null` | required | When it last signed in or confirmed a change. |
| `backed_up` | `boolean` | required | Synced by its password manager to the person’s other devices. |
| `device_type` | `"singleDevice" \| "multiDevice"` |  | multiDevice: a synced passkey. singleDevice: bound to one device or security key. |

## PasskeyCredentialRequest

| Field | Type | | Description |
| --- | --- | --- | --- |
| `credential` | `Record<string, unknown>` | required | The browser’s AuthenticationResponseJSON. |

## PasskeyOptionsResponse

| Field | Type | | Description |
| --- | --- | --- | --- |
| `options` | `Record<string, unknown>` | required | PublicKeyCredentialRequestOptionsJSON: pass to navigator.credentials.get(). |

## PasskeyPromptRequest

| Field | Type | | Description |
| --- | --- | --- | --- |
| `action` | `"not_now" \| "never"` | required | not_now: ask again in 14 days. never: don’t ask again. |

## PasskeyRegistrationOptionsResponse

| Field | Type | | Description |
| --- | --- | --- | --- |
| `options` | `Record<string, unknown>` | required | PublicKeyCredentialCreationOptionsJSON: pass to navigator.credentials.create(). |

## PasskeySignInOptionsRequest

| Field | Type | | Description |
| --- | --- | --- | --- |
| `next` | `string` |  | Same-origin path to land on after sign-in. Anything else is ignored. Default /app. |
| `invitation_token` | `string` |  | An invitation token (from /invite/{token}): signing in accepts the invitation. |

## PendingOrganizationRequest

| Field | Type | | Description |
| --- | --- | --- | --- |
| `organization_id` | `string` | required | One of the organizations from GET /auth/pending. |

## PendingSignInResponse

The sign-in in progress in this browser.

| Field | Type | | Description |
| --- | --- | --- | --- |
| `step` | `"code" \| "organization" \| "verify_email"` | required | code: waiting for the emailed sign-in code. organization: pick an organization. verify_email: enter the code that verifies the email. |
| `email` | `string` |  | The address signing in, when known. |
| `organizations` | `object[]` |  | For organization: the organizations to pick from. |
| `invitation` | `boolean` | required | Signing in accepts an invitation. |

## PendingVerifyRequest

| Field | Type | | Description |
| --- | --- | --- | --- |
| `code` | `string` | required | The 6-digit code from the email. Spaces and dashes are ignored. |

## PlanLimits

The allowances that apply to the organization.

| Field | Type | | Description |
| --- | --- | --- | --- |
| `messages_per_month` | `integer \| null` | required | Inbound messages and outbound recipients together per calendar month (UTC). null: no combined cap (paid plans). |
| `messages_per_day` | `integer \| null` | required | The same, per UTC day. null: no daily cap. |
| `inbound_per_month` | `integer \| null` | required | Inbound messages included per month on paid plans (more is billed as overage). null on Free. |
| `outbound_per_month` | `integer \| null` | required | Outbound recipients included per month on paid plans (more is billed as overage). null on Free. |
| `storage_bytes` | `integer` | required | Storage included, in bytes (1 GB = 1,000,000,000 bytes). |
| `custom_domains` | `integer` | required | Custom domains included. |
| `seats` | `integer \| null` | required | Members (including pending invitations). null: unlimited. |
| `overage` | `boolean` | required | true: usage past an allowance is billed, never blocked. false (Free): inbound past it is held and sending is refused until the reset. |

## PlanUsage

Plan allowances and usage of the organization in the current period.

| Field | Type | | Description |
| --- | --- | --- | --- |
| `plan` | `"free" \| "pro" \| "team" \| "scale"` | required | Plan of the organization: free, pro, team or scale. |
| `period` | `object` | required |  |
| `limits` | `PlanLimits` | required | The allowances that apply to the organization. |
| `usage` | `object` | required |  |
| `held` | `object` | required |  |
| `enforcement` | `"enforce" \| "shadow" \| "off" \| "exempt"` | required | enforce: limits apply. shadow and off: limits are not applied right now (usage is still counted). exempt: this organization has its own limits (unclaimed agent sandbox). |

## Pod

| Field | Type | | Description |
| --- | --- | --- | --- |
| `pod_id` | `string` | required | ID of the pod. |
| `organization_id` | `string` | required | ID of the organization. |
| `name` | `string` | required | Name of the pod. |
| `is_default` | `boolean` | required | Whether this is the default pod, where inboxes go when no pod is given. It cannot be deleted. |
| `client_id` | `string` |  | Client ID given at creation (idempotency key). |
| `updated_at` | `string (date-time)` | required | Time at which the pod was last updated. |
| `created_at` | `string (date-time)` | required | Time at which the pod was created. |

## PreviewTrustedDomainRequest

| Field | Type | | Description |
| --- | --- | --- | --- |
| `domain` | `string` | required | A domain (example.com) or an exact address (invoices@example.com). Unicode names are accepted and stored in ASCII (punycode) form. (min length 1, max length 320) |
| `include_subdomains` | `boolean` |  | As for create. |

## PreviewTrustedDomainResponse

What a create would do, without saving anything.

| Field | Type | | Description |
| --- | --- | --- | --- |
| `domain` | `string \| null` | required | The entry as it would be stored (ASCII form). null when it is not a domain or address. |
| `domain_unicode` | `string \| null` | required | The same in Unicode form. |
| `entry_type` | `object` | required |  |
| `allowed` | `boolean` | required | Whether a create would be accepted (with confirm: true when confirmation_required), plan limit aside. |
| `refusal` | `("free_mail" \| "privacy_relay" \| "shared_platform" \| "public_suffix" \| "disposable" \| "invalid" \| "confusable_idn" \| null) \| null` | required | Why it cannot be trusted. null when it can. |
| `message` | `string \| null` | required | The refusal or confirmation, in one sentence. null when there is neither. |
| `confirmation_required` | `boolean` | required | A platform where anyone can write the content, or a relay address: a create needs confirm: true. |
| `warnings` | `Warning[]` | required | Conflicts with block entries or allow-only scopes, as a create would report them. |
| `would_release` | `object \| null` | required | Held messages of the last 30 days that apply_to_existing would release at this level. null when the entry is not allowed. |
| `suggestions` | `string[]` | required | Narrower entries to consider instead: exact addresses that have written from this domain, most frequent first, at most 5. Empty when there are none. |
| `evidence` | `Evidence` | required | What MachineMail has seen of the domain, for warnings before trusting it. All false, null or 0 when the entry is not a domain or address. |

## ProtectedResourceMetadata

RFC 9728 OAuth Protected Resource Metadata.

| Field | Type | | Description |
| --- | --- | --- | --- |
| `resource` | `string` | required | The protected resource: <issuer>/ (the REST API and MCP), or <issuer>/<path> for a path-suffixed document. On the MCP server’s own host, the MCP server’s URL (https://mcp.machinemail.dev/mcp); on the issuer, <issuer>/mcp is the legacy MCP alias. |
| `authorization_servers` | `string[]` | required |  |
| `scopes_supported` | `("inboxes.read" \| "inboxes.write" \| "messages.read" \| "messages.send" \| "messages.write" \| "webhooks.read" \| "webhooks.write" \| "domains.read" \| "domains.write" \| "lists.read" \| "lists.write" \| "metrics.read" \| "pods.read" \| "pods.write" \| "api_keys.read" \| "api_keys.write")[]` | required |  |
| `bearer_methods_supported` | `string[]` | required | header. |
| `resource_name` | `string` | required |  |
| `resource_logo_uri` | `string` | required |  |
| `resource_documentation` | `string` | required | The /auth.md document, or the MCP guide for <issuer>/mcp. |

## RatePoint

| Field | Type | | Description |
| --- | --- | --- | --- |
| `timestamp` | `string (date-time)` | required | End of the window the point covers; the window is the window seconds before it. |
| `rate` | `number` |  | Bounced (or complained) messages divided by messages sent over the window, as a fraction (0.05 is 5%). Absent when nothing was sent in the window. |
| `sent` | `integer` | required | Messages sent in the window. |

## ReadyResponse

| Field | Type | | Description |
| --- | --- | --- | --- |
| `status` | `"ready" \| "unavailable"` | required | ready when storage answers; the HTTP status is then 200, otherwise 503. |
| `checks` | `object` | required | The checks behind status. |

## Recipient

| Field | Type | | Description |
| --- | --- | --- | --- |
| `address` | `string` | required | Recipient address. |
| `status` | `string` | required | bounced or complained. |

## RegisterPasskeyRequest

| Field | Type | | Description |
| --- | --- | --- | --- |
| `credential` | `Record<string, unknown>` | required | The browser’s RegistrationResponseJSON. |
| `name` | `string` |  | Name for the passkey (up to 64 characters). Default: its password manager or device, or “Passkey”. |

## Reject

| Field | Type | | Description |
| --- | --- | --- | --- |
| `inbox_id` | `string` | required | ID of the inbox. |
| `thread_id` | `null` | required | Always null: nothing was stored. |
| `message_id` | `null` | required | Always null: nothing was stored. |
| `timestamp` | `string (date-time)` | required | When it happened. |
| `reason` | `"inbox_paused" \| "plan_limit" \| "plan_limit_expired"` | required | Why the message was rejected. inbox_paused: the inbox is paused. plan_limit (MachineMail): the organization reached a Free plan limit and already holds as much mail as it may. plan_limit_expired (MachineMail): held mail was not released in time and was deleted. |
| `from` | `string` | required | Sender of the rejected message. |
| `subject` | `string` |  | Subject of the rejected message. |

## RemovePasskeyResponse

| Field | Type | | Description |
| --- | --- | --- | --- |
| `ok` | `true` | required |  |
| `sessions_ended` | `integer` | required | Sessions the passkey had signed in that were signed out. |
| `signed_out` | `boolean` | required | This session was one of them: sign in again. |

## RenamePasskeyRequest

| Field | Type | | Description |
| --- | --- | --- | --- |
| `name` | `string` | required | New name, 1 to 64 characters. |

## ReplyAllMessageRequest

| Field | Type | | Description |
| --- | --- | --- | --- |
| `labels` | `string \| string[]` |  | Labels of the message. SENT is always added. |
| `reply_to` | `string \| string[]` |  | Reply-to address or addresses. |
| `text` | `string` |  | Plain text body. |
| `html` | `string` |  | HTML body. |
| `attachments` | `SendAttachment[]` |  | Attachments to include. |
| `headers` | `Record<string, string>` |  | Headers to include. In-Reply-To and References also thread the message. |
| `to` | `string \| string[]` |  | More recipients, added to the sender and recipients of the original. |
| `cc` | `string \| string[]` |  | More CC recipients, added to the CC recipients of the original. |
| `bcc` | `string \| string[]` |  | BCC recipient address or addresses. |
| `subject` | `string` |  | Subject of the reply. Defaults to the original subject with Re: in front. |

## ReplyToMessageRequest

| Field | Type | | Description |
| --- | --- | --- | --- |
| `labels` | `string \| string[]` |  | Labels of the message. SENT is always added. |
| `reply_to` | `string \| string[]` |  | Reply-to address or addresses. |
| `to` | `string \| string[]` |  | Recipient address or addresses. Defaults to the sender of the original (its reply-to addresses if any); for an outbound original, its recipients. |
| `cc` | `string \| string[]` |  | CC recipient address or addresses. |
| `bcc` | `string \| string[]` |  | BCC recipient address or addresses. |
| `reply_all` | `boolean` |  | true replies to all recipients of the original, as Reply All Message does; to and cc are then added to them. |
| `text` | `string` |  | Plain text body. |
| `html` | `string` |  | HTML body. |
| `attachments` | `SendAttachment[]` |  | Attachments to include. |
| `headers` | `Record<string, string>` |  | Headers to include. In-Reply-To and References also thread the message. |
| `subject` | `string` |  | Subject of the reply. Defaults to the original subject with Re: in front. |

## RevokeOtherSessionsResponse

| Field | Type | | Description |
| --- | --- | --- | --- |
| `ok` | `true` | required |  |
| `ended` | `integer` | required | Other sessions signed out. |

## RevokeRequest

| Field | Type | | Description |
| --- | --- | --- | --- |
| `token` | `string` | required | An access token, a refresh token or an identity assertion. |
| `token_type_hint` | `string` |  | access_token, refresh_token or identity_assertion. A refresh token revokes its whole grant; an identity assertion its whole registration. Other values are ignored. |
| `client_id` | `string` |  | Refresh and access tokens of an app: when given, must be the client the token was issued to, or nothing is revoked. |
| `client_secret` | `string` |  | A confidential client authenticates as at the token endpoint (client_secret_post, or HTTP Basic). |

## ScopeDescription

| Field | Type | | Description |
| --- | --- | --- | --- |
| `scope` | `"inboxes.read" \| "inboxes.write" \| "messages.read" \| "messages.send" \| "messages.write" \| "webhooks.read" \| "webhooks.write" \| "domains.read" \| "domains.write" \| "lists.read" \| "lists.write" \| "metrics.read" \| "pods.read" \| "pods.write" \| "api_keys.read" \| "api_keys.write"` | required | OAuth scope. Each grants API key permissions: inboxes.* (inbox_*), messages.read (message, thread and label reads), messages.send (send, reply, drafts), messages.write (update and delete messages and threads), webhooks.*, domains.*, lists.* (allow/block lists and suppressions), pods.*, api_keys.*, metrics.read (metrics and the event log). |
| `description` | `string` | required |  |

## SearchInboxesResponse

| Field | Type | | Description |
| --- | --- | --- | --- |
| `count` | `integer` | required | Number of items returned. |
| `limit` | `integer` |  | Maximum number of items per page. Default 50, maximum 500; larger values are capped. |
| `next_page_token` | `string` |  | Page token for pagination. Absent on the last page. |
| `inboxes` | `Inbox[]` | required | Matching inboxes, newest first. |
| `truncated` | `true` |  | Present when the organization has more than 500 inboxes in scope: only the newest 500 were searched. |

## SearchMessagesResponse

| Field | Type | | Description |
| --- | --- | --- | --- |
| `count` | `integer` | required | Number of items returned. |
| `limit` | `integer` |  | Maximum number of items per page. Default 50, maximum 500; larger values are capped. |
| `next_page_token` | `string` |  | Page token for pagination. Absent on the last page. |
| `messages` | `MessageItem[]` | required | Matching messages, newest first unless ascending is set. |

## SearchThreadsResponse

| Field | Type | | Description |
| --- | --- | --- | --- |
| `count` | `integer` | required | Number of items returned. |
| `limit` | `integer` |  | Maximum number of items per page. Default 50, maximum 500; larger values are capped. |
| `next_page_token` | `string` |  | Page token for pagination. Absent on the last page. |
| `threads` | `ThreadItem[]` | required | Matching threads, ordered by timestamp, newest first unless ascending is set. |
| `truncated` | `true` |  | Present when q matched more than 500 messages: only the threads of the newest 500 matching messages were considered. |

## Send

| Field | Type | | Description |
| --- | --- | --- | --- |
| `inbox_id` | `string` | required | ID of the inbox. |
| `thread_id` | `string` | required | ID of the thread. |
| `message_id` | `string` | required | ID of the message. |
| `timestamp` | `string (date-time)` | required | When it happened. |
| `recipients` | `string[]` | required | Every recipient (to, cc and bcc). |

## SendAttachment

Provide either `content` or `url` for each attachment.

| Field | Type | | Description |
| --- | --- | --- | --- |
| `filename` | `string` |  | Filename of the attachment. Defaults to the last segment of url. |
| `content_type` | `string` |  | Content type of the attachment. Defaults to the Content-Type of the url response. |
| `content_disposition` | `"inline" \| "attachment"` |  | Content disposition of the attachment. |
| `content_id` | `string` |  | Content ID, for inline images referenced as cid: in the HTML body. |
| `content` | `string` |  | Base64 encoded content of the attachment. |
| `url` | `string` |  | URL the API downloads the attachment from: public http(s) only, no redirects, at most 10 MB, 20 second timeout. |

## SendMessageRequest

| Field | Type | | Description |
| --- | --- | --- | --- |
| `labels` | `string \| string[]` |  | Labels of the message. SENT is always added. |
| `reply_to` | `string \| string[]` |  | Reply-to address or addresses. |
| `to` | `string \| string[]` |  | Recipient address or addresses. |
| `cc` | `string \| string[]` |  | CC recipient address or addresses. |
| `bcc` | `string \| string[]` |  | BCC recipient address or addresses. |
| `subject` | `string` |  | Subject of the message. |
| `text` | `string` |  | Plain text body. |
| `html` | `string` |  | HTML body. |
| `attachments` | `SendAttachment[]` |  | Attachments to include. |
| `headers` | `Record<string, string>` |  | Headers to include. In-Reply-To and References also thread the message. |

## SendMessageResponse

| Field | Type | | Description |
| --- | --- | --- | --- |
| `message_id` | `string` | required | ID of the message. |
| `thread_id` | `string` | required | ID of the thread. |
| `timestamp` | `string (date-time)` | required | When the message was sent. |

## SessionResponse

The signed-in user and their organization.

| Field | Type | | Description |
| --- | --- | --- | --- |
| `user` | `User` | required | A person who signs in to the dashboard. |
| `organization` | `Organization` | required | The organization of the credential. plan and GET /v0/organizations/usage report plan allowances; inboxes are unlimited (no inbox_limit) and there are no billing fields. |

## SetError

RFC 8935 SET delivery error.

| Field | Type | | Description |
| --- | --- | --- | --- |
| `err` | `string` | required | invalid_request, invalid_issuer, invalid_key or invalid_audience. |
| `description` | `string` | required |  |

## SignInStepResponse

The result of a sign-in step.

| Field | Type | | Description |
| --- | --- | --- | --- |
| `ok` | `true` | required |  |
| `signed_in` | `boolean` | required | true: the mm_session cookie is set. false: another step comes first (organization picker, email verification, SSO). |
| `redirect_to` | `string` | required | Where to send the browser next, built by the server: next (with welcome=1 after a first sign-in), /login/organization, /login/verify, an identity provider, or /login?error=<code>. |

## SignupRequest

| Field | Type | | Description |
| --- | --- | --- | --- |
| `email` | `string` | required |  |
| `password` | `string` | required | At least 8 characters. |
| `name` | `string` |  | Display name of the user. |
| `org_name` | `string` |  | Organization name. |
| `organization_name` | `string` |  | Alias of org_name. |
| `organizationName` | `string` | deprecated | Alias of org_name. |

## SignupResponse

The new user, their organization and the first organization API key (shown once).

| Field | Type | | Description |
| --- | --- | --- | --- |
| `user` | `User` | required | A person who signs in to the dashboard. |
| `organization` | `Organization` | required | The organization of the credential. plan and GET /v0/organizations/usage report plan allowances; inboxes are unlimited (no inbox_limit) and there are no billing fields. |
| `api_key` | `OneTimeApiKey` | required | An API key with its plaintext secret, returned once. |

## StartOrganizationDeletionRequest

| Field | Type | | Description |
| --- | --- | --- | --- |
| `confirm_name` | `string` | required | The name of the organization, exactly as it is shown (case and spaces included). |

## StepUpEmailResponse

| Field | Type | | Description |
| --- | --- | --- | --- |
| `ok` | `true` | required |  |
| `email` | `string` | required | Where the code went, masked. |
| `expires_at` | `string (date-time)` | required | When the code expires (10 minutes). |

## StepUpEmailVerifyRequest

| Field | Type | | Description |
| --- | --- | --- | --- |
| `code` | `string` | required | The 6-digit code from the email. Spaces and dashes are ignored. |

## StepUpResponse

| Field | Type | | Description |
| --- | --- | --- | --- |
| `ok` | `true` | required |  |
| `recent_until` | `string (date-time)` | required | Until when sensitive changes need no further confirmation. |

## Suggestion

A sender the organization regularly corresponds with.

| Field | Type | | Description |
| --- | --- | --- | --- |
| `domain` | `string` | required | The domain, or for entry_type: "email" the address, as a create would take it (ASCII form). |
| `entry_type` | `"domain" \| "email"` | required | domain: every address at the domain (and its subdomains with include_subdomains). email: exactly one address. |
| `message_count` | `integer` | required | Messages exchanged with it in the last 90 days: received with its own verified DKIM signature, plus sent to it. |
| `last_seen_at` | `string (date-time)` | required | When the last of those messages was received or sent. |
| `dkim_seen` | `boolean` | required | Mail from it carried its own DKIM signature that our server verified. |

## SuggestionsResponse

Senders this organization regularly corresponds with (mail received in the last 90 days with their own verified DKIM signature, or sent to them) that may be trusted and are not already trusted or blocked at this level. For free-mail and other domains that cannot be trusted whole, the exact address is suggested instead.

| Field | Type | | Description |
| --- | --- | --- | --- |
| `count` | `integer` | required | Number of suggestions returned. |
| `suggestions` | `Suggestion[]` | required | Most messages first. |

## Suppression

An address MachineMail will not send to. Suppressions apply to the whole organization.

| Field | Type | | Description |
| --- | --- | --- | --- |
| `email` | `string` | required | The suppressed address (lowercased). |
| `organization_id` | `string` | required | ID of the organization. |
| `reason` | `"bounce" \| "complaint" \| "manual"` | required | bounce: a permanent bounce. complaint: the recipient reported spam; only MachineMail support can remove it. manual: added with POST /v0/suppressions. |
| `detail` | `string` |  | Free text: the bounce diagnostic, or the note given when the address was added manually. |
| `source_message_id` | `string` |  | Message whose bounce or complaint caused the suppression. |
| `created_at` | `string (date-time)` | required | When the address was suppressed. |

## SwitchOrganizationRequest

| Field | Type | | Description |
| --- | --- | --- | --- |
| `organization_id` | `string` | required | ID of an organization the person belongs to. |

## Thread

| Field | Type | | Description |
| --- | --- | --- | --- |
| `inbox_id` | `string` | required | ID of the inbox. |
| `thread_id` | `string` | required | ID of the thread. |
| `labels` | `string[]` | required | Labels of the thread: the union of the labels of its messages. |
| `timestamp` | `string (date-time)` | required | Timestamp of the last sent or received message. |
| `received_timestamp` | `string (date-time)` |  | Timestamp of the last received message. |
| `sent_timestamp` | `string (date-time)` |  | Timestamp of the last sent message. |
| `senders` | `string[]` | required | Senders in the thread. In format username@domain.com. |
| `recipients` | `string[]` | required | Recipients (to and cc) in the thread. In format username@domain.com. |
| `subject` | `string` |  | Subject of the thread. |
| `preview` | `string` |  | Text preview of the last message in the thread. |
| `attachments` | `Attachment[]` |  | Attachments in the thread. |
| `last_message_id` | `string` | required | ID of the last message in the thread. |
| `message_count` | `integer` | required | Number of messages in the thread. |
| `size` | `integer` | required | Size of the thread in bytes. |
| `updated_at` | `string (date-time)` | required | Time at which the thread was last updated. |
| `created_at` | `string (date-time)` | required | Time at which the thread was created. |
| `pod_id` | `string` | required | ID of the pod. |
| `count` | `integer` | required | Number of messages in this page. |
| `limit` | `integer` |  | Maximum number of messages requested for this page. |
| `next_page_token` | `string` |  | Token for the next, older page of messages. Absent when this page completes the thread. |
| `messages` | `Message[]` | required | Messages in this page, oldest first. The first page has the newest messages; follow next_page_token for older ones. |

## ThreadItem

| Field | Type | | Description |
| --- | --- | --- | --- |
| `inbox_id` | `string` | required | ID of the inbox. |
| `thread_id` | `string` | required | ID of the thread. |
| `labels` | `string[]` | required | Labels of the thread: the union of the labels of its messages. |
| `timestamp` | `string (date-time)` | required | Timestamp of the last sent or received message. |
| `received_timestamp` | `string (date-time)` |  | Timestamp of the last received message. |
| `sent_timestamp` | `string (date-time)` |  | Timestamp of the last sent message. |
| `senders` | `string[]` | required | Senders in the thread. In format username@domain.com. |
| `recipients` | `string[]` | required | Recipients (to and cc) in the thread. In format username@domain.com. |
| `subject` | `string` |  | Subject of the thread. |
| `preview` | `string` |  | Text preview of the last message in the thread. |
| `attachments` | `Attachment[]` |  | Attachments in the thread. |
| `last_message_id` | `string` | required | ID of the last message in the thread. |
| `message_count` | `integer` | required | Number of messages in the thread. |
| `size` | `integer` | required | Size of the thread in bytes. |
| `updated_at` | `string (date-time)` | required | Time at which the thread was last updated. |
| `created_at` | `string (date-time)` | required | Time at which the thread was created. |
| `pod_id` | `string` | required | ID of the pod. |

## TokenRequest

| Field | Type | | Description |
| --- | --- | --- | --- |
| `grant_type` | `string` | required | authorization_code (with PKCE) and refresh_token for apps and MCP clients; urn:ietf:params:oauth:grant-type:device_code for mm login; urn:ietf:params:oauth:grant-type:jwt-bearer (RFC 7523) exchanges an assertion; urn:workos:agent-auth:grant-type:claim polls with a claim_token. Other values: unsupported_grant_type. |
| `code` | `string` |  | authorization_code: the code from the authorization response. Single use; expires after 2 minutes. |
| `redirect_uri` | `string` |  | authorization_code: the redirect_uri of the authorization request, when it had one. |
| `code_verifier` | `string` |  | authorization_code: the PKCE code verifier (43 to 128 characters). |
| `refresh_token` | `string` |  | refresh_token: the current refresh token. Each use returns a new one; presenting a replaced one within 60 seconds of its replacement returns the current one, later it revokes the whole grant. |
| `scope` | `string` |  | refresh_token, optional: narrow the new access token to these scopes of the grant. |
| `device_code` | `string` |  | device_code: the device_code from /auth/cli/device/code. |
| `assertion` | `string` |  | jwt-bearer grant: the identity_assertion. |
| `client_id` | `string` |  | authorization_code and refresh_token: the client the code or token was issued to (invalid_grant otherwise); may instead come from an HTTP Basic Authorization header (client_secret_basic), or be left out (taken from the code or token). device_code: required. jwt-bearer, optional: the registration id. |
| `client_secret` | `string` |  | Confidential clients using client_secret_post (registered with a secret). Alternatively send Authorization: Basic base64(urlencode(client_id):urlencode(client_secret)) (client_secret_basic), not both. A missing or wrong secret: 401 invalid_client. Public clients need none; one they send is ignored. |
| `resource` | `string` |  | RFC 8707. authorization_code and refresh_token: must be the resource of the authorization request (invalid_target otherwise). jwt-bearer, optional audience pin: must be <issuer>/. |
| `claim_token` | `string` |  | Claim grant: the claim_token from registration. |

## TokenResponse

auth.md grants issue no refresh\_token (exchange the identity\_assertion again); the authorization code and refresh grants do.

| Field | Type | | Description |
| --- | --- | --- | --- |
| `access_token` | `string` | required | JWT (at+jwt) to send as Authorization: Bearer. Tokens from the authorization code grant are audience-bound: aud the MCP server (https://mcp.machinemail.dev/mcp, or the legacy <issuer>/mcp) works on the MCP endpoint only (either URL), <issuer>/ on /v0 and the MCP endpoint. auth.md tokens work on both. |
| `token_type` | `"Bearer"` | required |  |
| `expires_in` | `integer` | required | Lifetime of the access token in seconds. |
| `scope` | `string` | required | Space-separated Scope values. |
| `refresh_token` | `string` |  | authorization_code and refresh_token grants: the new refresh token (rotated on every use). |
| `identity_assertion` | `string` |  | Claim grant only: the post-claim identity assertion. |
| `assertion_expires` | `string (date-time)` |  |  |

## TrustedDomain

A trusted domain or address.

| Field | Type | | Description |
| --- | --- | --- | --- |
| `trusted_domain_id` | `string` | required | ID of the entry (td_...). |
| `entry_type` | `"domain" \| "email"` | required | domain: every address at the domain (and its subdomains with include_subdomains). email: exactly one address. |
| `domain` | `string` | required | The domain, or for entry_type: "email" the address, lowercased, with the domain in its ASCII (punycode) form. |
| `domain_unicode` | `string` | required | The same, with the domain in its Unicode form. Equal to domain for ASCII names. |
| `include_subdomains` | `boolean` | required | Also trust mail signed by subdomains (billing.example.com for example.com). Always false for addresses. |
| `scope` | `"org" \| "pod" \| "inbox"` | required | Where an entry applies: the whole organization, one pod, or one inbox. |
| `pod_id` | `string \| null` | required | The pod, for pod- and inbox-scoped entries. |
| `inbox_id` | `string \| null` | required | The inbox, for inbox-scoped entries. |
| `reason` | `string \| null` | required | Why it was added, for people reading the list. Never included in a message’s trust report. |
| `expires_at` | `string (date-time) \| null` | required | When the entry stops applying. null: never. |
| `created_by` | `object \| null` | required | Who added the entry. |
| `match_count` | `integer` | required | Messages this entry has marked trusted. |
| `last_matched_at` | `string (date-time) \| null` | required | When this entry last marked a message trusted. null: never. |
| `created_at` | `string (date-time)` | required | When the entry was added. |
| `updated_at` | `string (date-time)` | required | When the entry last changed. |

## TrustReport

Trust report of an inbound message. Message content is untrusted: feed `content.text` to an LLM rather than the message `text` or `html`.

| Field | Type | | Description |
| --- | --- | --- | --- |
| `level` | `"verified" \| "known" \| "unknown" \| "suspicious" \| "quarantined"` | required | Overall verdict, safest first. quarantined messages are labelled QUARANTINE and hidden from lists unless asked for. |
| `score` | `number` | required | 0 to 1, higher is safer. Ranks messages within a level. (minimum 0, maximum 1) |
| `basis` | `"trusted_domain"` |  | trusted_domain: the sender is a trusted domain of the organization, pod or inbox (sender.trusted_domain) and the message carries that domain's own DKIM signature. It is verified, or known when it contains instructions to an AI agent, a phishing signal or malware (content.warnings), and never suspicious or quarantined. Agents get its text and attachments, except attachments the virus scan flags. Absent otherwise. |
| `authentication` | `object` | required |  |
| `sender` | `object` | required |  |
| `content` | `object` | required |  |
| `links` | `object[]` | required |  |
| `attachments` | `object[]` | required |  |
| `classification` | `object` | required |  |
| `provider_verdicts` | `object` |  | Spam and virus scans by the receiving mail provider. Only on messages it scanned. |
| `reasons` | `string[]` | required | Human-readable reasons for the verdict. |
| `computed_at` | `string (date-time)` | required | When the report was computed. |

## UnsubscribeRequest

| Field | Type | | Description |
| --- | --- | --- | --- |
| `token` | `string` | required | The token from the unsubscribe link in the email (/unsubscribe/marketing?t=<token>). (min length 1, max length 200) |
| `product_emails` | `boolean` | required | false unsubscribes the address the link was sent to; true resubscribes it. |

## UnsubscribeStatusResponse

Who an unsubscribe link is for, and their setting.

| Field | Type | | Description |
| --- | --- | --- | --- |
| `email` | `string` | required | The address the link was sent to, masked (a••@example.com). |
| `product_emails` | `boolean` | required | The address gets getting-started emails now. |

## UpdateApiKeyRequest

Fields you omit are unchanged. `permissions` replaces the permission object in full. Scope and expiry cannot be changed.

| Field | Type | | Description |
| --- | --- | --- | --- |
| `name` | `string` |  | New name. A blank name is ignored. |
| `permissions` | `object` |  | New permissions; replaces the permission object in full. |

## UpdateDomainRequest

| Field | Type | | Description |
| --- | --- | --- | --- |
| `feedback_enabled` | `boolean` |  | Stored and returned; has no effect. |
| `inbound_enabled` | `boolean` |  | Receive email for this domain. false makes the inbound MX record optional and re-evaluates the status. |
| `catch_all` | `boolean` |  | Create an inbox for any address at the domain that receives mail. |
| `catch_all_ttl_seconds` | `integer \| null` |  | TTL of inboxes created by catch-all from now on. null makes them permanent. |

## UpdateDraftRequest

| Field | Type | | Description |
| --- | --- | --- | --- |
| `reply_to` | `string \| string[] \| null` |  | Reply-to addresses. null clears them. |
| `to` | `string \| string[] \| null` |  | Recipient addresses. null clears them. |
| `cc` | `string \| string[] \| null` |  | CC addresses. null clears them. |
| `bcc` | `string \| string[] \| null` |  | BCC addresses. null clears them. |
| `subject` | `string \| null` |  | Subject. null clears it. |
| `text` | `string \| null` |  | Plain text body. null clears it. |
| `html` | `string \| null` |  | HTML body. null clears it. |
| `add_attachments` | `SendAttachment[]` |  | Attachments to add. Each needs content or url. |
| `remove_attachments` | `string[]` |  | IDs of attachments to remove. Unknown IDs are ignored. |
| `add_labels` | `string[]` |  | Labels to add. |
| `remove_labels` | `string[]` |  | Labels to remove (applied after add_labels). |
| `send_at` | `string (date-time) \| null` |  | Schedule (or reschedule) the draft. null unschedules it. |

## UpdateInboxRequest

| Field | Type | | Description |
| --- | --- | --- | --- |
| `display_name` | `string` |  | Display name: Display Name <username@domain.com>. |
| `status` | `"active" \| "paused"` |  | Whether the inbox sends and receives mail. paused stops both: sends return 403 (Inbox <inbox_id> is paused), and incoming mail is not stored; a message.rejected event with reason inbox_paused is emitted instead. Mail already in the inbox, its drafts and its API keys are kept. Set active to resume. |
| `metadata` | `(Record<string, string \| number \| boolean \| null>) \| null` |  | Merged into the existing metadata: keys you send are added or overwritten, keys set to null are removed, other keys are kept. null clears all metadata. |
| `ttl_seconds` | `integer \| null` |  | Make the inbox ephemeral with this TTL (expires_at restarts from now), or null to make it permanent. |
| `ephemeral` | `boolean` |  | false makes the inbox permanent (same as ttl_seconds: null). true alone has no effect; send ttl_seconds. |

## UpdateMemberRequest

| Field | Type | | Description |
| --- | --- | --- | --- |
| `role` | `"owner" \| "admin" \| "member"` | required | Role in the organization. Owners and admins invite, change roles and remove members; only owners grant or remove the owner role, and an organization always keeps one owner. |

## UpdateMessageRequest

| Field | Type | | Description |
| --- | --- | --- | --- |
| `add_labels` | `string \| string[]` |  | Label or labels to add. |
| `remove_labels` | `string \| string[]` |  | Label or labels to remove. |

## UpdateMessageResponse

| Field | Type | | Description |
| --- | --- | --- | --- |
| `message_id` | `string` | required | ID of the message. |
| `labels` | `string[]` | required | Labels of the message after the update. |

## UpdateOrganizationRequest

| Field | Type | | Description |
| --- | --- | --- | --- |
| `name` | `string` |  | New name. Must not be blank. |
| `slug` | `string` |  | Immutable: the slug is fixed when the organization is created. Only the current value is accepted (a client echoing the organization back); any other value is a 400. |
| `undo_send` | `boolean` |  | Undo send in the dashboard: when true (the default), a message sent from the MachineMail dashboard waits 5 seconds before it goes out, so it can be undone. Only the dashboard reads it: the API, the MCP server and the CLI always send at once. |
| `default_inbox_id` | `string \| null` |  | Make this inbox (an inbox_id of the organization) the default inbox, or null to have none. An unknown inbox is a 404. |

## UpdatePodRequest

| Field | Type | | Description |
| --- | --- | --- | --- |
| `name` | `string` |  | New name. Must not be blank. |

## UpdateThreadRequest

| Field | Type | | Description |
| --- | --- | --- | --- |
| `add_labels` | `string \| string[]` |  | Label or labels to add to every message in the thread. |
| `remove_labels` | `string \| string[]` |  | Label or labels to remove from every message in the thread. Applied after add_labels. |

## UpdateTrustedDomainRequest

| Field | Type | | Description |
| --- | --- | --- | --- |
| `include_subdomains` | `boolean` |  | Also trust mail signed by subdomains. Not allowed for an address. |
| `confirm` | `boolean` |  | Required (true) to turn on include_subdomains when a platform where anyone can write the content lies under the domain: without it the answer is 422 confirmation_required. |
| `reason` | `string \| null` |  | New reason; null clears it. (max length 500) |
| `expires_at` | `string (date-time) \| null` |  | New expiry, in the future; null removes it. |

## UpdateWebhookHeadersRequest

Sets, replaces or removes custom delivery headers; the headers you do not name are unchanged. Give at least one of `headers` (each name is added, or replaces the value of the header of that name in any case) or `remove_headers`. A name cannot be both set and removed in one request.

| Field | Type | | Description |
| --- | --- | --- | --- |
| `headers` | `Record<string, string>` |  | Custom HTTP headers sent with every delivery to this webhook (every attempt, retry, replay and test event), alongside the X-MachineMail-* headers, for example {"Authorization": "Bearer <token>"} for an endpoint that requires one. Values are write-only: they are stored encrypted and no response returns them; reads list the names (header_names). The headers are not part of the signed payload: X-MachineMail-Signature covers the body only. At most 10 headers. Names are HTTP tokens of up to 64 characters, compared without regard to case; values are printable ASCII (no line breaks or control characters) of up to 4096 characters, with surrounding spaces removed, and names plus values total at most 8192 bytes. Reserved names are refused with 400: Host, Content-Length, Content-Type, Content-Encoding, Transfer-Encoding, Connection, Upgrade, TE, Trailer, Keep-Alive, Expect, User-Agent, Accept-Encoding, and every name starting with Proxy-, Sec-, X-MachineMail-, Svix- or Webhook-. Authorization and Cookie are allowed. |
| `remove_headers` | `string[]` |  | Names of custom headers to remove (any case). Names that are not set are ignored. |

## UpdateWebhookRequest

| Field | Type | | Description |
| --- | --- | --- | --- |
| `event_types` | `("message.received" \| "message.received.spam" \| "message.received.quarantined" \| "message.received.unauthenticated" \| "message.received.blocked" \| "message.sent" \| "message.delivered" \| "message.bounced" \| "message.complained" \| "message.rejected" \| "domain.verified" \| "domain.updated" \| "inbox.created" \| "inbox.expired" \| "draft.sent" \| "webhook.test" \| "trusted_domain.created" \| "trusted_domain.updated" \| "trusted_domain.deleted" \| "message.released")[]` |  | Replaces the subscribed event types in full (not a merge). An empty array subscribes to every event type except domain.updated, message.released and trusted_domain.*, which are sent only when listed. Omit it to keep the types. |
| `enabled` | `boolean` |  | false disables the webhook: no new deliveries are queued (deliveries already queued are still attempted). true enables it again. Events that occurred while it was disabled are not delivered later. |
| `add_inbox_ids` | `string[]` |  | Inboxes to add to inbox_ids. |
| `remove_inbox_ids` | `string[]` |  | Inboxes to remove from inbox_ids. |
| `add_pod_ids` | `string[]` |  | Pods to add to pod_ids. |
| `remove_pod_ids` | `string[]` |  | Pods to remove from pod_ids. |
| `url` | `string` |  | New endpoint URL: a public http(s) URL. |
| `headers` | `Record<string, string> \| null` |  | Replaces the custom delivery headers in full (not a merge): the webhook then sends exactly these. null or {} removes them all; omit it to keep them. To change one header and keep the others, use PATCH /v0/webhooks/{webhook_id}/headers. Values are write-only. At most 10 headers. Names are HTTP tokens of up to 64 characters, compared without regard to case; values are printable ASCII (no line breaks or control characters) of up to 4096 characters, with surrounding spaces removed, and names plus values total at most 8192 bytes. Reserved names are refused with 400: Host, Content-Length, Content-Type, Content-Encoding, Transfer-Encoding, Connection, Upgrade, TE, Trailer, Keep-Alive, Expect, User-Agent, Accept-Encoding, and every name starting with Proxy-, Sec-, X-MachineMail-, Svix- or Webhook-. Authorization and Cookie are allowed. |

## UsagePoint

| Field | Type | | Description |
| --- | --- | --- | --- |
| `timestamp` | `string (date-time)` | required | Time of the value (end). |
| `value` | `integer` | required | Value of the usage metric now. |

## User

A person who signs in to the dashboard.

| Field | Type | | Description |
| --- | --- | --- | --- |
| `user_id` | `string` | required | ID of the user. |
| `email` | `string` | required | Email address the user signs in with. |
| `name` | `string \| null` |  | Display name, when set. |
| `organization_id` | `string` | required | ID of the organization this record is about: the session’s active organization, or the organization listed. |
| `role` | `"owner" \| "admin" \| "member"` | required | Role in the organization. Owners and admins invite, change roles and remove members; only owners grant or remove the owner role, and an organization always keeps one owner. |
| `created_at` | `string (date-time)` | required | When the user was created. |

## VerificationRecord

| Field | Type | | Description |
| --- | --- | --- | --- |
| `type` | `"TXT" \| "CNAME" \| "MX"` | required |  |
| `name` | `string` | required | Name (host) of the record. |
| `value` | `string` | required | Value of the record. |
| `status` | `"MISSING" \| "INVALID" \| "VALID"` | required | MISSING: not published yet. INVALID: published with the wrong value. |
| `priority` | `integer` |  | Priority of an MX record. |
| `reason` | `string` |  | Why an INVALID record is invalid: Found: <values seen>. |
| `purpose` | `"inbound" \| "spf" \| "dkim" \| "dmarc" \| "provider"` | required | What the record is for. provider records come from the sending provider. |
| `required` | `boolean` | required | Whether the record must be valid for the domain to verify. The inbound MX record is not required when inbound_enabled is false. |

## Warning

A conflict that does not stop the entry from being saved, but means some of its mail is still blocked.

| Field | Type | | Description |
| --- | --- | --- | --- |
| `code` | `"shadowed_by_block" \| "shadowed_by_allow_only"` | required | shadowed_by_block: a receive/block list entry matches some or all of the mail this entry trusts, and a block always wins over trust. shadowed_by_allow_only: the scope is allow-only (it has receive/allow entries) and none of them allows this sender, so the mail is still BLOCKED. |
| `scope` | `"org" \| "pod" \| "inbox"` | required | Where an entry applies: the whole organization, one pod, or one inbox. |
| `pod_id` | `string` |  | The pod of the list, for pod and inbox scopes. |
| `inbox_id` | `string` |  | The inbox of the list, for the inbox scope. |
| `entry` | `object` |  | shadowed_by_block only: the block entry. Left out for credentials without list_entry_read. |
| `message` | `string` | required | What the warning means, in one sentence. |

## Webhook

| Field | Type | | Description |
| --- | --- | --- | --- |
| `webhook_id` | `string` | required | ID of the webhook. |
| `url` | `string` | required | URL of the webhook endpoint. |
| `event_types` | `("message.received" \| "message.received.spam" \| "message.received.quarantined" \| "message.received.unauthenticated" \| "message.received.blocked" \| "message.sent" \| "message.delivered" \| "message.bounced" \| "message.complained" \| "message.rejected" \| "domain.verified" \| "domain.updated" \| "inbox.created" \| "inbox.expired" \| "draft.sent" \| "webhook.test" \| "trusted_domain.created" \| "trusted_domain.updated" \| "trusted_domain.deleted" \| "message.released")[]` | required | Event types the webhook receives. Empty: every event type except domain.updated, message.released and trusted_domain.*, which are sent only when listed. |
| `pod_ids` | `string[]` |  | Only events of these pods. Absent: every pod in scope. |
| `inbox_ids` | `string[]` |  | Only events of these inboxes. Absent: every inbox in scope. |
| `pod_id` | `string` |  | Set when the webhook was created with a pod-scoped credential (or under /v0/pods/{pod_id}): it never receives events outside that pod. |
| `inbox_id` | `string` |  | Set when the webhook was created with an inbox-scoped credential (or under /v0/inboxes/{inbox_id}): it only receives events of that inbox. |
| `secret` | `string` | required | Signing secret (whsec_...) for X-MachineMail-Signature. Returned on every read. |
| `enabled` | `boolean` | required | Whether new events are delivered. Webhooks are never disabled automatically. |
| `updated_at` | `string (date-time)` | required | Time at which the webhook was last updated. |
| `created_at` | `string (date-time)` | required | Time at which the webhook was created. |
| `client_id` | `string` |  | Client ID given at creation (idempotency key). |
| `status` | `"active" \| "disabled"` | required | active or disabled; mirrors enabled. |
| `header_names` | `string[]` | required | Names of the custom delivery headers (headers), as they were set; empty when there are none. Values are write-only and never returned. |
| `headers_updated_at` | `string (date-time)` |  | When the custom headers were last set or changed. Absent when they never were. |

## WebhookDelivery

| Field | Type | | Description |
| --- | --- | --- | --- |
| `delivery_id` | `string` | required | ID of the delivery (X-MachineMail-Delivery-Id). |
| `webhook_id` | `string` | required | ID of the webhook. |
| `event_id` | `string` | required | ID of the event (X-MachineMail-Event-Id). |
| `event_type` | `"message.received" \| "message.received.spam" \| "message.received.quarantined" \| "message.received.unauthenticated" \| "message.received.blocked" \| "message.sent" \| "message.delivered" \| "message.bounced" \| "message.complained" \| "message.rejected" \| "domain.verified" \| "domain.updated" \| "inbox.created" \| "inbox.expired" \| "draft.sent" \| "webhook.test" \| "trusted_domain.created" \| "trusted_domain.updated" \| "trusted_domain.deleted" \| "message.released"` | required | Type of event. AgentMail has no message.received.quarantined, domain.updated, inbox.created, inbox.expired, draft.sent or webhook.test; MachineMail does not emit message.opened or calendar.event.*. domain.updated is sent whenever a DNS check changes the domain status or a record status (the change to VERIFIED also sends domain.verified). trusted_domain.created, .updated and .deleted (MachineMail) record changes to trusted domains. message.released (MachineMail) is sent for each held message a new trusted domain released (apply_to_existing), instead of message.received. Webhooks and WebSocket subscriptions receive domain.updated, message.released and the trusted_domain.* types only when their event_types list them, while the SSE stream and the event log always include them. |
| `attempt` | `integer` | required | Number of attempts so far. |
| `status` | `"pending" \| "delivered" \| "failed"` | required | pending until a 2xx response (delivered), or failed when retries stop (5 days after the event) or the webhook or event was deleted. |
| `response_status` | `integer` |  | HTTP status of the last attempt. |
| `response_body` | `string` |  | First 1024 bytes of the last response body. |
| `next_attempt_at` | `string (date-time)` |  | When the next attempt is due, while pending. |
| `last_error` | `string` |  | Error of the last failed attempt (HTTP 500, a network error). |
| `delivered_at` | `string (date-time)` |  | When the delivery succeeded. |
| `created_at` | `string (date-time)` | required | When the delivery was queued. |
| `updated_at` | `string (date-time)` | required | Last change to the delivery. |

## WebhookHeaderNamesResponse

| Field | Type | | Description |
| --- | --- | --- | --- |
| `header_names` | `string[]` | required | Names of the custom delivery headers of this webhook, as they were set. Values are never returned. |
| `headers_updated_at` | `string (date-time)` |  | When the custom headers were last set or changed. Absent when they never were. |
