reference.md

API overview

MachineMail API conventions: the base URL, /v0 versioning, authentication, requests and responses, pagination, idempotency, request IDs, CORS and scoped routes.

MachineMail is an email API for AI agents. The /v0 HTTP API is wire-compatible with AgentMail's /v0 API: the same paths, snake_case field names, enums and pagination. MachineMail adds trust reports, ephemeral and catch-all inboxes, delivery replay, an event log and live event streams. See AgentMail compatibility.

The endpoint reference is generated from the OpenAPI document, served at GET /v0/openapi.json.

Base URL

https://api.machinemail.dev
Surface URL
REST API https://api.machinemail.dev/v0/...
Remote MCP server https://mcp.machinemail.dev/mcp (MCP tools)
Server-sent events https://api.machinemail.dev/v0/events/stream
WebSocket wss://api.machinemail.dev/v0/ws
Agent onboarding (auth.md) https://api.machinemail.dev/auth.md

Agent inboxes on the shared domain have addresses like agent@machinemail.dev.

Versioning

Every resource endpoint sits under /v0. There is no version header. Health checks (/health), the MCP endpoint (/mcp), the agent onboarding endpoints (/agent/*, /oauth2/*, /.well-known/*) and the dashboard session endpoints (/auth/*) are unversioned.

Authentication

Send an API key as a bearer token:

curl https://api.machinemail.dev/v0/inboxes \
  -H "Authorization: Bearer $MACHINEMAIL_API_KEY"

Keys look like mm_live_ followed by 32 base62 characters. A key belongs to an organization and is scoped to the whole organization, one pod, or one inbox. It can carry a permission allowlist; without one it can do everything its scope allows. Agent access tokens issued through the auth.md flow are accepted in the same header. See Authentication.

A missing or invalid credential returns 401 with a WWW-Authenticate: Bearer resource_metadata="https://api.machinemail.dev/.well-known/oauth-protected-resource" header. A resource outside the key's scope returns 404, not 403.

Requests

Responses

Pagination

List endpoints take limit (default 50, max 500), page_token and ascending, and return count, limit and next_page_token. See Pagination.

Idempotency

There is no Idempotency-Key header. Instead, creates accept a client_id field:

Resource Endpoint client_id behaviour
Inbox POST /v0/inboxes dedupes
Draft POST /v0/inboxes/{inbox_id}/drafts dedupes
Webhook POST /v0/webhooks dedupes
Domain POST /v0/domains dedupes
Pod POST /v0/pods dedupes
API key POST /v0/api-keys stored, but not deduped: every call creates a key

When a resource in the organization already has the client_id, the API returns that resource with 200 instead of creating a new one (201). The request body is not compared, so a retry with different fields still returns the original resource. client_id values are unique per organization, not per pod or inbox. A request limited to a pod or an inbox only gets back a resource it could read: when the client_id belongs to a resource outside that pod or inbox, the API returns 409 conflict without that resource. A request is limited by a pod- or inbox-scoped API key, and also by a route under /v0/pods/{pod_id} or /v0/inboxes/{inbox_id} even with an organization key.

Sending mail is not idempotent: retrying messages/send after a timeout can send twice. To make a send safe to retry, create a draft with a client_id, then send the draft.

Request IDs

The API does not set or read a request ID header. When you report a problem, include the time, the method and path, and any resource IDs.

CORS

The API does not allow cross-origin browser requests: call it from your server, never from a web page that holds your API key.

Scoped routes

Every /v0 resource route is also served under /v0/pods/{pod_id}/..., which narrows the request to that pod. Webhook, list, metric, event and API key routes are also served under /v0/inboxes/{inbox_id}/..., narrowed to that inbox. The OpenAPI document describes the unprefixed form (and the prefixed form for lists). Creating an API key under a prefix scopes the new key to that pod or inbox.