{"openapi":"3.0.3","info":{"title":"MultiMail API","version":"2.0.0","description":"Email-as-a-Service for AI agents. Inbound email converted to markdown, outbound markdown converted to HTML. Built on Cloudflare Workers."},"servers":[{"url":"https://api.multimail.dev"}],"paths":{"/v1/slug-check/{slug}":{"get":{"summary":"Check slug availability","description":"Check if a slug is available for registration. Returns suggestions if taken or reserved. No auth required.","parameters":[{"name":"slug","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Slug is available or unavailable with suggestions","content":{"application/json":{"schema":{"type":"object","properties":{"available":{"type":"boolean"},"slug":{"type":"string"},"reason":{"type":"string"},"suggestions":{"type":"array","items":{"type":"string"}},"address_preview":{"type":"string"}}}}}}}}},"/v1/account/challenge":{"post":{"summary":"Request proof-of-work challenge for signup","description":"Returns an ALTCHA challenge. Solve it and include the solution as pow_solution in POST /v1/account. Challenge expires in 5 minutes.","requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"oversight_email":{"type":"string","format":"email","description":"Optional. Calibrates difficulty based on email domain."},"attribution":{"$ref":"#/components/schemas/AttributionPayload","description":"Optional conversion attribution. Same bounded payload accepted by signup; used only for source-keyed funnel challenge counts."}}}}}},"responses":{"200":{"description":"Challenge issued","content":{"application/json":{"schema":{"type":"object","properties":{"algorithm":{"type":"string","example":"SHA-256"},"challenge":{"type":"string"},"maxnumber":{"type":"integer"},"salt":{"type":"string"},"signature":{"type":"string"}}}}}}}}},"/v1/account/did-challenge":{"post":{"summary":"Issue an account-binding DID challenge","description":"DID bridge (optional). POST agent_did (an Ed25519 did:key) and receive {challenge_id, nonce}. Sign the UTF-8 of MM-DID-BIND:v1\\n<nonce>\\n<agent_did>\\n<normalize(oversight_email)>\\ndid:web:multimail.dev (single LF joins, no trailing newline) with the DID's Ed25519 key, then pass agent_did + did_challenge_id + did_signature to POST /v1/account. Single-use; expires in 5 minutes. No auth required.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["agent_did"],"properties":{"agent_did":{"type":"string","maxLength":256,"description":"did:key:z… encoding an Ed25519 public key."}}}}}},"responses":{"200":{"description":"Challenge issued","content":{"application/json":{"schema":{"type":"object","properties":{"challenge_id":{"type":"string","maxLength":64},"nonce":{"type":"string","maxLength":64},"expires_in":{"type":"integer"}}}}}},"400":{"description":"agent_did missing or not a valid Ed25519 did:key"},"429":{"description":"Rate limited (shared per-IP challenge budget)"}}}},"/v1/account":{"post":{"summary":"Create a new account (deferred provisioning)","description":"Requires a solved proof-of-work challenge. Creates a pending signup and sends a confirmation email. Response is always identical for privacy (anti-enumeration). Honors an optional Idempotency-Key request header (UUID) to safely retry without creating duplicate pending_signups rows.","parameters":[{"name":"Idempotency-Key","in":"header","required":false,"schema":{"type":"string","maxLength":200},"description":"Client-generated UUID. Replays return the cached response (1hr TTL). In-flight retries return 409."}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SignupRequest"}}}},"responses":{"200":{"description":"Confirmation sent (privacy-preserving — does not confirm account creation)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SignupResponse"}}}},"409":{"description":"Concurrent request with same Idempotency-Key in flight."}}},"get":{"summary":"Get current tenant info and usage","description":"Requires read scope.","security":[{"bearerAuth":[]}],"responses":{"200":{"description":"Tenant info","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AccountInfo"}}}}}},"patch":{"summary":"Update tenant settings","description":"Requires admin scope.","security":[{"bearerAuth":[]}],"requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string","maxLength":200},"oversight_email":{"type":"string","format":"email","maxLength":320},"physical_address":{"type":"string","maxLength":2000},"approval_code":{"type":"string","maxLength":16}}}}}},"responses":{"200":{"description":"Settings updated"}}},"delete":{"summary":"Permanently delete tenant account","description":"Hard-deletes all tenant data (mailboxes, emails, API keys, usage, audit log). Frees the slug for re-registration. Requires admin scope.","security":[{"bearerAuth":[]}],"requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"approval_code":{"type":"string","maxLength":16}}}}}},"responses":{"200":{"description":"Account deleted","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"string","example":"deleted"}}}}}},"401":{"description":"Missing or invalid API key"},"403":{"description":"Requires admin scope"}}}},"/v1/operator/start-session":{"post":{"summary":"Start an operator dashboard session","description":"Requires admin scope. Sends a one-time code to the oversight email and begins the operator-session OTP flow.","security":[{"bearerAuth":[]}],"requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{}}}}},"responses":{"202":{"description":"Code sent","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"string","maxLength":32},"masked_email":{"type":"string","maxLength":320}}}}}},"400":{"description":"No oversight email configured"},"429":{"description":"Rate limited"},"503":{"description":"Code email could not be delivered — retry shortly (rate-limit slot is not consumed)"}}}},"/v1/operator/verify-session":{"post":{"summary":"Verify an operator dashboard session","description":"Requires admin scope. Exchanges a one-time code for a short-lived HttpOnly operator-session cookie.","security":[{"bearerAuth":[]}],"requestBody":{"content":{"application/json":{"schema":{"type":"object","required":["code"],"properties":{"code":{"type":"string","maxLength":16}}}}}},"responses":{"200":{"description":"Session active","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"string","maxLength":32},"expires_at":{"type":"integer"}}}}}},"403":{"description":"Invalid code"},"404":{"description":"No pending code"}}}},"/v1/operator/end-session":{"post":{"summary":"End an operator dashboard session","description":"Requires admin scope. Clears the operator-session cookie.","security":[{"bearerAuth":[]}],"requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{}}}}},"responses":{"200":{"description":"Session cleared"}}}},"/v1/operator/session":{"get":{"summary":"Get operator-session status","description":"Requires admin scope. Reports whether the current browser has an active operator-session cookie.","security":[{"bearerAuth":[]}],"responses":{"200":{"description":"Operator session status","content":{"application/json":{"schema":{"type":"object","properties":{"active":{"type":"boolean"},"expires_at":{"type":"integer","nullable":true},"max_tier":{"type":"string","enum":["medium"],"nullable":true}}}}}},"401":{"description":"Missing or invalid API key"},"403":{"description":"Requires admin scope"}}}},"/v1/support":{"post":{"summary":"Send a support message","description":"Public endpoint. Requires a solved ALTCHA proof-of-work payload. Sends a message to support@multimail.dev.","requestBody":{"content":{"application/json":{"schema":{"type":"object","required":["name","email","subject","message","pow_solution"],"properties":{"name":{"type":"string","maxLength":200},"email":{"type":"string","format":"email","maxLength":320},"subject":{"type":"string","maxLength":200},"message":{"type":"string","maxLength":16384},"pow_solution":{"type":"object","required":["algorithm","challenge","number","salt","signature"],"properties":{"algorithm":{"type":"string","maxLength":32},"challenge":{"type":"string","maxLength":512},"number":{"type":"integer"},"salt":{"type":"string","maxLength":512},"signature":{"type":"string","maxLength":512}}}}}}}},"responses":{"200":{"description":"Support message sent","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"string","maxLength":32}}}}}},"400":{"description":"Invalid proof-of-work or request body"},"413":{"description":"Body too large"},"429":{"description":"Rate limited"}}}},"/v1/account/resend-confirmation":{"post":{"summary":"Resend operator confirmation email","description":"Public endpoint (no auth required). Resends the activation email with a new code for unconfirmed accounts. Rate limited to 1 request per 10 minutes per email. Response is identical whether or not a pending signup exists (anti-enumeration).","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["oversight_email"],"properties":{"oversight_email":{"type":"string","format":"email","maxLength":320,"description":"The oversight email used at signup."}}}}}},"responses":{"200":{"description":"Confirmation resent (privacy-preserving — returned even if no pending signup exists)","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"string","example":"confirmation_resent"},"message":{"type":"string"}}}}}},"400":{"description":"oversight_email missing"},"429":{"description":"Rate limited — wait 10 minutes"},"503":{"description":"Confirmation email could not be delivered — previously emailed code stays valid; cooldown is cleared so an immediate retry is allowed"}}}},"/v1/feedback":{"post":{"summary":"Submit feedback (bug report, feature request)","description":"Requires any authenticated API key (no specific scope). Stores the feedback and notifies support. Rate limited to 3 submissions per tenant per hour. Body limited to 16KB.","security":[{"bearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["type","subject","description"],"properties":{"type":{"type":"string","enum":["tool_bug","site_problem","feature_request","other"]},"subject":{"type":"string"},"description":{"type":"string"},"tool_name":{"type":"string","description":"Optional. MCP tool name the feedback concerns."},"error_message":{"type":"string","description":"Optional. Error message observed."}}}}}},"responses":{"201":{"description":"Feedback received","content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"string"},"status":{"type":"string","example":"received"}}}}}},"400":{"description":"Invalid JSON, unknown type, or missing subject/description"},"413":{"description":"Body larger than 16KB"},"429":{"description":"Rate limited (3 per tenant per hour)"}}}},"/v1/setup/verify-token":{"get":{"summary":"Verify a guided-setup token","description":"Public endpoint (no auth required). Validates the HMAC-signed setup token from an approval email and returns the operator email it was issued for.","parameters":[{"name":"token","in":"query","required":true,"schema":{"type":"string"},"description":"Signed setup token from the approval email"}],"responses":{"200":{"description":"Token is valid","content":{"application/json":{"schema":{"type":"object","properties":{"email":{"type":"string","format":"email"},"valid":{"type":"boolean","example":true}}}}}},"400":{"description":"token parameter missing, or token invalid/expired"}}}},"/v1/proof-status":{"get":{"summary":"Get Lean proof verification status","description":"Public endpoint (no auth required). Reports whether the Lean 4 authorization proofs were verified in CI and the timestamp of the last verification. Cached for 1 hour.","responses":{"200":{"description":"Proof verification status","content":{"application/json":{"schema":{"type":"object","properties":{"verified":{"type":"boolean"},"date":{"type":"string","nullable":true,"description":"ISO timestamp of the last successful proof verification, or null"}}}}}}}}},"/v1/mailboxes":{"post":{"summary":"Create a new mailbox","description":"Requires admin scope + operator approval. Address can be a local part (appended to tenant subdomain) or full address on a verified custom domain. The first call without approval_code returns 202 with an approval code sent to the oversight email; resubmit with approval_code to complete.","security":[{"bearerAuth":[]}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateMailboxRequest"}}}},"responses":{"201":{"description":"Mailbox created","content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"string"},"address":{"type":"string"}}}}}},"202":{"description":"Operator approval required — approval code sent to oversight email; resubmit with approval_code","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"string","example":"pending_approval"},"message":{"type":"string"},"masked_email":{"type":"string"},"reason":{"type":"string","enum":["operator_session_required","approval_required"]}}}}}},"400":{"description":"Invalid address format or no oversight email configured"},"403":{"description":"Plan mailbox limit reached, invalid approval code, or wrong scope"},"409":{"description":"Address already in use"}}},"get":{"summary":"List all mailboxes","description":"Requires read scope.","security":[{"bearerAuth":[]}],"responses":{"200":{"description":"Mailbox list","content":{"application/json":{"schema":{"type":"object","properties":{"mailboxes":{"type":"array","items":{"$ref":"#/components/schemas/Mailbox"}}}}}}}}}},"/v1/mailboxes/{mailboxId}":{"patch":{"summary":"Update mailbox settings","description":"Requires admin scope. Oversight mode can only be downgraded here; upgrades require the upgrade flow. Changing auto_cc/auto_bcc (operator-controlled mail-routing settings) requires operator approval: the first call without approval_code returns 202 with an approval challenge sent to the oversight email; resubmit with approval_code to complete.","security":[{"bearerAuth":[]}],"parameters":[{"name":"mailboxId","in":"path","required":true,"schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateMailboxRequest"}}}},"responses":{"200":{"description":"Updated","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"string","example":"updated"}}}}}},"202":{"description":"Operator approval required (auto_cc/auto_bcc change) — approval code sent to oversight email; resubmit with approval_code","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"string","example":"pending_approval"},"message":{"type":"string"},"masked_email":{"type":"string"}}}}}},"400":{"description":"Invalid field value"},"404":{"description":"Mailbox not found"}}},"delete":{"summary":"Soft-delete a mailbox","description":"Requires admin scope.","security":[{"bearerAuth":[]}],"parameters":[{"name":"mailboxId","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Deleted","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"string","example":"deleted"}}}}}},"404":{"description":"Mailbox not found"}}}},"/v1/mailboxes/{mailboxId}/configure":{"patch":{"summary":"Configure mailbox settings (guided-setup variant of mailbox update)","description":"Requires admin scope. Updates any of: oversight_mode (downgrade only — upgrades require the upgrade flow), display_name, auto_cc, auto_bcc, signature_block, default_gate_timing, scheduling_enabled, mcp_configured, ai_disclosure. Changing auto_cc/auto_bcc (operator-controlled mail-routing settings) requires operator approval: the first call without approval_code returns 202 with an approval code sent to the oversight email; resubmit with approval_code to complete.","security":[{"bearerAuth":[]}],"parameters":[{"name":"mailboxId","in":"path","required":true,"schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"oversight_mode":{"type":"string","enum":["read_only","gated_all","gated_send","monitored","autonomous"],"description":"Downgrade only. To upgrade, use POST /v1/mailboxes/{id}/request-upgrade."},"display_name":{"type":"string","nullable":true},"auto_cc":{"type":"string","format":"email","nullable":true,"description":"Operator-controlled. Changing this requires the two-step approval_code flow."},"auto_bcc":{"type":"string","format":"email","nullable":true,"description":"Operator-controlled. Changing this — including clearing to null — requires the two-step approval_code flow."},"signature_block":{"type":"string","nullable":true},"default_gate_timing":{"type":"string","enum":["gate_first","schedule_first"]},"scheduling_enabled":{"type":"boolean"},"mcp_configured":{"type":"boolean"},"ai_disclosure":{"type":"boolean"},"approval_code":{"type":"string","description":"Operator approval code for gated changes (auto_cc/auto_bcc). Omit on the first call to receive a code at the oversight email; resubmit with it to complete."}}}}}},"responses":{"200":{"description":"Updated mailbox","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Mailbox"}}}},"202":{"description":"Operator approval required (auto_cc/auto_bcc change) — approval code sent to oversight email; resubmit with approval_code","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"string","example":"pending_approval"},"message":{"type":"string"},"masked_email":{"type":"string"},"reason":{"type":"string","enum":["operator_session_required","approval_required"]}}}}}},"400":{"description":"Invalid field value, no fields to update, or invalid request body"},"403":{"description":"Oversight mode upgrade attempted via this endpoint, invalid approval code, or wrong scope"},"404":{"description":"Mailbox not found"}}}},"/v1/mailboxes/{mailboxId}/emails":{"get":{"summary":"List emails in a mailbox","description":"Requires read scope. Returns paginated email summaries (no body content).","security":[{"bearerAuth":[]}],"parameters":[{"name":"mailboxId","in":"path","required":true,"schema":{"type":"string"}},{"name":"status","in":"query","schema":{"type":"string","enum":["unread","read","archived","deleted","pending_send_approval","pending_inbound_approval","rejected","cancelled","send_failed","scheduled","spam_flagged","spam_quarantined"]}},{"name":"limit","in":"query","schema":{"type":"integer","minimum":1,"maximum":100,"default":20}},{"name":"cursor","in":"query","schema":{"type":"string"},"description":"Pagination cursor (received_at epoch from previous page)"},{"name":"sender","in":"query","schema":{"type":"string"},"description":"Filter by sender address"},{"name":"subject_contains","in":"query","schema":{"type":"string"},"description":"Substring search on subject"},{"name":"date_after","in":"query","schema":{"type":"string"},"description":"Filter emails after this date"},{"name":"date_before","in":"query","schema":{"type":"string"},"description":"Filter emails before this date"},{"name":"direction","in":"query","schema":{"type":"string","enum":["inbound","outbound"]}},{"name":"has_attachments","in":"query","schema":{"type":"string","enum":["true","false"]}},{"name":"since_id","in":"query","schema":{"type":"string"},"description":"Return emails newer than this ID"}],"responses":{"200":{"description":"Email list","content":{"application/json":{"schema":{"$ref":"#/components/schemas/EmailListResponse"}}}}}}},"/v1/emails":{"get":{"summary":"List spam emails across tenant","description":"Requires read scope. Without a status filter, returns spam_flagged and spam_quarantined emails across all tenant mailboxes.","security":[{"bearerAuth":[]}],"parameters":[{"name":"status","in":"query","schema":{"type":"string","enum":["spam_flagged","spam_quarantined"]}},{"name":"limit","in":"query","schema":{"type":"integer","minimum":1,"maximum":100,"default":20}},{"name":"cursor","in":"query","schema":{"type":"string"},"description":"Pagination cursor (received_at epoch from previous page)"}],"responses":{"200":{"description":"Spam email list","content":{"application/json":{"schema":{"$ref":"#/components/schemas/EmailListResponse"}}}}}}},"/v1/emails/{id}/report-spam":{"post":{"summary":"Report an email as spam","description":"Requires send scope. Moves the email to spam_quarantined and records a user spam label.","security":[{"bearerAuth":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Email marked as spam"},"404":{"description":"Email not found"}}}},"/v1/emails/{id}/not-spam":{"post":{"summary":"Mark an email as not spam","description":"Requires send scope. Restores the email to unread and records a user not_spam label.","security":[{"bearerAuth":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Email restored to unread"},"404":{"description":"Email not found"}}}},"/v1/mailboxes/{mailboxId}/emails/{emailId}":{"get":{"summary":"Get full email with markdown body and attachment metadata","description":"Requires read scope. Automatically marks unread emails as read.","security":[{"bearerAuth":[]}],"parameters":[{"name":"mailboxId","in":"path","required":true,"schema":{"type":"string"}},{"name":"emailId","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Full email","content":{"application/json":{"schema":{"$ref":"#/components/schemas/EmailResponse"}}}},"404":{"description":"Email not found"}}}},"/v1/mailboxes/{mailboxId}/emails/{emailId}/attachments/{filename}":{"get":{"summary":"Download individual attachment","description":"Requires read scope. Streams the attachment bytes directly (Content-Type from stored metadata, Content-Disposition: attachment). For a shareable time-limited link instead, use the /url variant of this route.","security":[{"bearerAuth":[]}],"parameters":[{"name":"mailboxId","in":"path","required":true,"schema":{"type":"string"}},{"name":"emailId","in":"path","required":true,"schema":{"type":"string"}},{"name":"filename","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Attachment binary stream","content":{"application/octet-stream":{"schema":{"type":"string","format":"binary"}}}},"400":{"description":"Invalid filename"},"404":{"description":"Mailbox, email, or attachment not found"}}}},"/v1/mailboxes/{mailboxId}/emails/{emailId}/attachments/{filename}/url":{"get":{"summary":"Generate a signed temporary attachment URL","description":"Requires read scope. Returns an HMAC-signed URL (served at GET /v1/attachments/{token}, no API key required) that expires after 1 hour.","security":[{"bearerAuth":[]}],"parameters":[{"name":"mailboxId","in":"path","required":true,"schema":{"type":"string"}},{"name":"emailId","in":"path","required":true,"schema":{"type":"string"}},{"name":"filename","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Signed URL issued","content":{"application/json":{"schema":{"type":"object","properties":{"url":{"type":"string","format":"uri"},"filename":{"type":"string"},"size_bytes":{"type":"integer"},"content_type":{"type":"string"},"expires_in":{"type":"integer","example":3600}}}}}},"400":{"description":"Invalid filename"},"404":{"description":"Mailbox, email, or attachment not found"}}}},"/v1/attachments/{token}":{"get":{"summary":"Download attachment via signed token (no auth)","description":"Public endpoint (no API key required). Serves the attachment binary for a valid HMAC-signed token issued by the /attachments/{filename}/url route. Tokens expire after 1 hour.","parameters":[{"name":"token","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Attachment binary stream","content":{"application/octet-stream":{"schema":{"type":"string","format":"binary"}}}},"400":{"description":"Malformed token or payload"},"403":{"description":"Invalid token signature"},"404":{"description":"Attachment not found"},"410":{"description":"Token expired"}}}},"/v1/mailboxes/{mailboxId}/emails/{emailId}/cancel":{"post":{"summary":"Cancel a pending email","description":"Cancels an email in pending_send_approval or pending_inbound_approval status. Idempotent if already cancelled. Requires send scope.","security":[{"bearerAuth":[]}],"parameters":[{"name":"mailboxId","in":"path","required":true,"schema":{"type":"string"}},{"name":"emailId","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Email cancelled","content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"string"},"status":{"type":"string","example":"cancelled"}}}}}},"404":{"description":"Email not found"},"409":{"description":"Email not in a cancellable state"}}}},"/v1/mailboxes/{mailboxId}/emails/{emailId}/schedule":{"patch":{"summary":"Edit a scheduled email before it fires","description":"Requires send scope. Only emails with status 'scheduled' can be edited. send_at-only reschedules are allowed on any scheduled email; recipient/content changes (to/cc/bcc/subject/markdown) are rejected with 409 once the email has been operator-approved (cancel and resubmit instead). bcc edits are accepted but stored recipients are to/cc only.","security":[{"bearerAuth":[]}],"parameters":[{"name":"mailboxId","in":"path","required":true,"schema":{"type":"string"}},{"name":"emailId","in":"path","required":true,"schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"send_at":{"type":"string","description":"New delivery time. ISO 8601 UTC (must end with Z), at least 1 minute in the future, within the plan's schedule-ahead window."},"to":{"type":"array","items":{"type":"string","format":"email"}},"cc":{"type":"array","items":{"type":"string","format":"email"}},"bcc":{"type":"array","items":{"type":"string","format":"email"}},"subject":{"type":"string"},"markdown":{"type":"string","description":"Replacement body in markdown. Overwrites the stored body in R2."}}}}}},"responses":{"200":{"description":"Updated email record (raw stored representation: to_addresses/cc_addresses are JSON-encoded strings, send_at is epoch ms)","content":{"application/json":{"schema":{"type":"object"}}}},"400":{"description":"Invalid request body, mistyped field, or invalid send_at"},"403":{"description":"send_at beyond the plan's schedule-ahead window, or wrong scope"},"404":{"description":"Mailbox or email not found"},"409":{"description":"Email is not in 'scheduled' status, or recipient/content change attempted on an approved email"}}}},"/v1/mailboxes/{mailboxId}/emails/{emailId}/tags":{"put":{"summary":"Set tags on an email","description":"Replaces all tags with the provided key-value pairs. Requires send scope.","security":[{"bearerAuth":[]}],"parameters":[{"name":"mailboxId","in":"path","required":true,"schema":{"type":"string"}},{"name":"emailId","in":"path","required":true,"schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"type":"object","required":["tags"],"properties":{"tags":{"type":"object","additionalProperties":{"type":"string"}}}}}}},"responses":{"200":{"description":"Tags after write","content":{"application/json":{"schema":{"type":"object","properties":{"tags":{"type":"object","additionalProperties":{"type":"string"}}}}}}}}},"get":{"summary":"Get tags on an email","description":"Requires read scope.","security":[{"bearerAuth":[]}],"parameters":[{"name":"mailboxId","in":"path","required":true,"schema":{"type":"string"}},{"name":"emailId","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Tag map","content":{"application/json":{"schema":{"type":"object","properties":{"tags":{"type":"object","additionalProperties":{"type":"string"}}}}}}}}}},"/v1/mailboxes/{mailboxId}/emails/{emailId}/tags/{key}":{"delete":{"summary":"Delete a single tag","description":"Requires send scope.","security":[{"bearerAuth":[]}],"parameters":[{"name":"mailboxId","in":"path","required":true,"schema":{"type":"string"}},{"name":"emailId","in":"path","required":true,"schema":{"type":"string"}},{"name":"key","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Tag deleted","content":{"application/json":{"schema":{"type":"object","properties":{"deleted":{"type":"boolean"}}}}}}}}},"/v1/mailboxes/{mailboxId}/threads/{threadId}":{"get":{"summary":"Get thread with all messages","description":"Returns thread metadata and all emails in the thread (without body content). Requires read scope.","security":[{"bearerAuth":[]}],"parameters":[{"name":"mailboxId","in":"path","required":true,"schema":{"type":"string"}},{"name":"threadId","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Thread detail","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ThreadResponse"}}}},"404":{"description":"Thread not found or no emails"}}}},"/v1/mailboxes/{mailboxId}/send":{"post":{"summary":"Send email (provide markdown, we convert to HTML)","description":"Requires send scope.","security":[{"bearerAuth":[]}],"parameters":[{"name":"mailboxId","in":"path","required":true,"schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SendEmailRequest"}}}},"responses":{"200":{"description":"Idempotent duplicate","content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"string"},"status":{"type":"string","example":"already_sent"},"idempotent":{"type":"boolean"}}}}}},"202":{"description":"Email queued for scanning and delivery","content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"string"},"status":{"type":"string","example":"pending_scan"},"thread_id":{"type":"string"},"message":{"type":"string"}}}}}},"400":{"description":"Validation error or content filtered"},"403":{"description":"Mailbox is in read_only mode"},"429":{"description":"Quota exceeded or rate limited"}}}},"/v1/mailboxes/{mailboxId}/reply/{emailId}":{"post":{"summary":"Reply to an email in thread","description":"Requires send scope. Supports attachments and idempotency keys.","security":[{"bearerAuth":[]}],"parameters":[{"name":"mailboxId","in":"path","required":true,"schema":{"type":"string"}},{"name":"emailId","in":"path","required":true,"schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ReplyRequest"}}}},"responses":{"200":{"description":"Idempotent duplicate","content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"string"},"status":{"type":"string","example":"already_sent"},"idempotent":{"type":"boolean"}}}}}},"202":{"description":"Reply queued","content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"string"},"status":{"type":"string","example":"pending_scan"},"thread_id":{"type":"string"}}}}}},"403":{"description":"Mailbox is in read_only mode"},"409":{"description":"Duplicate reply detected (same content within 60s)"}}}},"/v1/contacts":{"post":{"summary":"Create a contact","description":"Add a contact to the address book. Requires send scope.","security":[{"bearerAuth":[]}],"requestBody":{"content":{"application/json":{"schema":{"type":"object","required":["name","email"],"properties":{"name":{"type":"string"},"email":{"type":"string","format":"email"},"tags":{"type":"array","items":{"type":"string"}}}}}}},"responses":{"201":{"description":"Contact created","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Contact"}}}},"400":{"description":"Name and email required"}}},"get":{"summary":"Search contacts","description":"Search address book by name or email. Omit query to list all. Requires read scope.","security":[{"bearerAuth":[]}],"parameters":[{"name":"q","in":"query","schema":{"type":"string"},"description":"Search by name or email (partial match)"}],"responses":{"200":{"description":"Contact list","content":{"application/json":{"schema":{"type":"object","properties":{"contacts":{"type":"array","items":{"$ref":"#/components/schemas/Contact"}}}}}}}}}},"/v1/contacts/{contactId}":{"delete":{"summary":"Delete a contact","description":"Requires admin scope.","security":[{"bearerAuth":[]}],"parameters":[{"name":"contactId","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Deleted","content":{"application/json":{"schema":{"type":"object","properties":{"deleted":{"type":"boolean"}}}}}},"404":{"description":"Contact not found"}}}},"/v1/oversight/pending":{"get":{"summary":"List emails pending oversight approval","description":"Requires oversight scope.","security":[{"bearerAuth":[]}],"responses":{"200":{"description":"Pending emails","content":{"application/json":{"schema":{"type":"object","properties":{"emails":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"mailbox_id":{"type":"string"},"direction":{"type":"string","enum":["inbound","outbound"]},"from":{"type":"string"},"to":{"type":"array","items":{"type":"string"}},"subject":{"type":"string"},"body_markdown":{"type":"string"},"status":{"type":"string"},"received_at":{"type":"string","format":"date-time"}}}}}}}}}}}},"/v1/oversight/decide":{"post":{"summary":"Approve or reject a pending email","description":"Requires oversight scope. Approved outbound emails are sent immediately.","security":[{"bearerAuth":[]}],"requestBody":{"content":{"application/json":{"schema":{"type":"object","required":["email_id","action"],"properties":{"email_id":{"type":"string"},"action":{"type":"string","enum":["approve","reject"]}}}}}},"responses":{"200":{"description":"Decision applied","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"string","enum":["approved","approved_and_sent","rejected"]},"postmark_message_id":{"type":"string"}}}}}},"404":{"description":"Email not found"}}}},"/v1/webhooks":{"post":{"summary":"Create a webhook subscription","description":"Subscribe to email events. Returns the signing secret (shown only on creation). Requires admin scope.","security":[{"bearerAuth":[]}],"requestBody":{"content":{"application/json":{"schema":{"type":"object","required":["url","events"],"properties":{"url":{"type":"string","format":"uri","description":"HTTPS URL to receive events"},"events":{"type":"array","items":{"type":"string"},"description":"Event types to subscribe to"},"mailbox_id":{"type":"string","description":"Optional: scope to a specific mailbox"}}}}}},"responses":{"201":{"description":"Subscription created","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WebhookSubscription"}}}},"400":{"description":"Invalid URL or events"}}},"get":{"summary":"List webhook subscriptions","description":"Requires admin scope. Signing secrets are not included in the list.","security":[{"bearerAuth":[]}],"responses":{"200":{"description":"Subscriptions","content":{"application/json":{"schema":{"type":"object","properties":{"subscriptions":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"url":{"type":"string"},"events":{"type":"array","items":{"type":"string"}},"mailbox_id":{"type":"string","nullable":true},"created_at":{"type":"string","format":"date-time"}}}}}}}}}}}},"/v1/webhooks/{id}":{"get":{"summary":"Get webhook subscription detail","description":"Includes signing secret. Requires admin scope.","security":[{"bearerAuth":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Subscription detail","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WebhookSubscription"}}}},"404":{"description":"Subscription not found"}}},"delete":{"summary":"Delete a webhook subscription","description":"Requires admin scope.","security":[{"bearerAuth":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Deleted","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"string","example":"deleted"}}}}}},"404":{"description":"Subscription not found"}}}},"/v1/webhooks/postmark-inbound":{"post":{"summary":"Postmark inbound email webhook","description":"Receives inbound emails from Postmark. Authenticated via HTTP Basic Auth with the Postmark webhook secret. Not a consumer API endpoint.","responses":{"200":{"description":"Processed","content":{"application/json":{"schema":{"type":"object","properties":{"received":{"type":"boolean"},"processed":{"type":"integer"}}}}}},"401":{"description":"Invalid credentials"}}}},"/v1/mailboxes/{mailboxId}/allowlist":{"get":{"summary":"List allowlist entries for a mailbox","description":"Any API key with read scope can list entries. Returns all sending allowlist patterns for the mailbox. The allowlist does not grant send permission — it only determines whether approved sends skip the oversight queue.","security":[{"bearerAuth":[]}],"parameters":[{"name":"mailboxId","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Allowlist entries","content":{"application/json":{"schema":{"type":"object","properties":{"entries":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"pattern":{"type":"string","description":"Email address or *@domain.com wildcard"},"added_by":{"type":"string"},"added_at":{"type":"integer"},"note":{"type":"string","nullable":true}}}},"mailbox_id":{"type":"string"},"count":{"type":"integer"}}}}}},"404":{"description":"Mailbox not found"}}},"post":{"summary":"Add an allowlist entry","description":"Requires admin scope + operator approval. Adds a recipient pattern (exact email or *@domain.com wildcard) that bypasses gated_send approval. Subject to plan-tier limits.","security":[{"bearerAuth":[]}],"parameters":[{"name":"mailboxId","in":"path","required":true,"schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"type":"object","required":["pattern"],"properties":{"pattern":{"type":"string","maxLength":254,"description":"Email address or *@domain.com wildcard"},"note":{"type":"string","maxLength":200,"description":"Optional note for audit trail"},"approval_code":{"type":"string","description":"Operator approval code (omit to request one)"}}}}}},"responses":{"201":{"description":"Entry added","content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"string"},"pattern":{"type":"string"},"note":{"type":"string","nullable":true},"added_at":{"type":"integer"}}}}}},"202":{"description":"Approval code sent to operator"},"400":{"description":"Invalid pattern or note"},"403":{"description":"Plan allowlist limit reached"},"409":{"description":"Pattern already allowlisted"},"429":{"description":"Rate limited — approval email already sent recently"}}}},"/v1/mailboxes/{mailboxId}/allowlist/{entryId}":{"delete":{"summary":"Remove an allowlist entry","description":"Requires admin scope + operator approval. Removes a sending allowlist pattern.","security":[{"bearerAuth":[]}],"parameters":[{"name":"mailboxId","in":"path","required":true,"schema":{"type":"string"}},{"name":"entryId","in":"path","required":true,"schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"approval_code":{"type":"string","description":"Operator approval code (omit to request one)"}}}}}},"responses":{"200":{"description":"Entry removed"},"202":{"description":"Approval code sent to operator"},"404":{"description":"Entry or mailbox not found"}}}},"/v1/mailboxes/{mailboxId}/rules":{"get":{"summary":"List inbox rules for a mailbox","description":"Requires read scope. Returns all active per-mailbox inbox rules. Rules are persistent server-side automation evaluated on inbound mail: block_sender drops matching mail before it enters the inbox, auto_archive stores mail but lands clean non-gated mail as archived, auto_tag applies a tag.","security":[{"bearerAuth":[]}],"parameters":[{"name":"mailboxId","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Active inbox rules","content":{"application/json":{"schema":{"type":"object","properties":{"rules":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"mailbox_id":{"type":"string"},"type":{"type":"string","enum":["block_sender","auto_archive","auto_tag"]},"pattern":{"type":"string","description":"Exact email or *@domain.com wildcard"},"tag_key":{"type":"string","nullable":true},"tag_value":{"type":"string","nullable":true},"created_by_key_id":{"type":"string","nullable":true},"created_at":{"type":"integer"},"is_active":{"type":"integer"}}}}}}}}},"404":{"description":"Mailbox not found"}}},"post":{"summary":"Create an inbox rule","description":"Requires send scope. Creates a persistent per-mailbox rule evaluated on inbound mail. type=block_sender drops matching mail before any storage; type=auto_archive lands clean non-gated mail as archived (never overrides spam quarantine or inbound oversight); type=auto_tag applies tag_key/tag_value. Pattern is an exact email or *@domain.com wildcard — the catch-all '*' is rejected. Duplicate active rules return 409.","security":[{"bearerAuth":[]}],"parameters":[{"name":"mailboxId","in":"path","required":true,"schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"type":"object","required":["type","pattern"],"properties":{"type":{"type":"string","enum":["block_sender","auto_archive","auto_tag"]},"pattern":{"type":"string","maxLength":254,"description":"Exact email or *@domain.com wildcard"},"tag_key":{"type":"string","maxLength":128,"description":"Required for auto_tag"},"tag_value":{"type":"string","maxLength":128,"description":"Required for auto_tag"}}}}}},"responses":{"201":{"description":"Rule created","content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"string"},"mailbox_id":{"type":"string"},"type":{"type":"string"},"pattern":{"type":"string"},"tag_key":{"type":"string","nullable":true},"tag_value":{"type":"string","nullable":true},"created_at":{"type":"integer"},"is_active":{"type":"integer"}}}}}},"400":{"description":"Invalid type, pattern, or tag fields"},"404":{"description":"Mailbox not found"},"409":{"description":"An identical active rule already exists"}}}},"/v1/mailboxes/{mailboxId}/rules/{ruleId}":{"delete":{"summary":"Delete an inbox rule","description":"Requires send scope. Soft-deletes the rule (sets is_active=0). Idempotent: deleting an already-inactive or missing rule returns deleted=false. Scoped to the caller's mailbox.","security":[{"bearerAuth":[]}],"parameters":[{"name":"mailboxId","in":"path","required":true,"schema":{"type":"string"}},{"name":"ruleId","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Deletion result","content":{"application/json":{"schema":{"type":"object","properties":{"deleted":{"type":"boolean"}}}}}},"404":{"description":"Mailbox not found"}}}},"/v1/mailboxes/{mailboxId}/request-upgrade":{"post":{"summary":"Request an oversight mode upgrade","description":"Requires send scope. Emails a one-time upgrade code to the operator. Rate-limited to 3 requests per hour per mailbox.","security":[{"bearerAuth":[]}],"parameters":[{"name":"mailboxId","in":"path","required":true,"schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"type":"object","required":["target_mode"],"properties":{"target_mode":{"type":"string","enum":["read_only","gated_all","gated_send","monitored","autonomous"]}}}}}},"responses":{"200":{"description":"Upgrade code sent to operator"},"400":{"description":"Invalid target_mode or already in that mode"},"429":{"description":"Too many upgrade requests"}}}},"/v1/mailboxes/{mailboxId}/upgrade":{"post":{"summary":"Redeem an upgrade code to change oversight mode","description":"Requires send scope. Codes are single-use and expire after 24 hours.","security":[{"bearerAuth":[]}],"parameters":[{"name":"mailboxId","in":"path","required":true,"schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"type":"object","required":["code"],"properties":{"code":{"type":"string","description":"Upgrade code from operator (e.g. A7X-29K)"}}}}}},"responses":{"200":{"description":"Mailbox oversight mode upgraded"},"403":{"description":"Invalid upgrade code"},"404":{"description":"No pending upgrade request"}}}},"/v1/api-keys":{"get":{"summary":"List API keys (never shows full key)","description":"Requires admin scope. Returns key prefix, scopes, and metadata.","security":[{"bearerAuth":[]}],"responses":{"200":{"description":"API key list","content":{"application/json":{"schema":{"type":"object","properties":{"api_keys":{"type":"array","items":{"$ref":"#/components/schemas/ApiKeyInfo"}}}}}}}}},"post":{"summary":"Create a new API key","description":"Requires admin scope. The raw key is returned only once in the response.","security":[{"bearerAuth":[]}],"requestBody":{"content":{"application/json":{"schema":{"type":"object","required":["name"],"properties":{"name":{"type":"string"},"scopes":{"type":"array","items":{"type":"string","enum":["read","send","admin","oversight"]},"default":["read"]}}}}}},"responses":{"201":{"description":"Key created","content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"string"},"api_key":{"type":"string","description":"Raw key (shown once)"},"prefix":{"type":"string"},"name":{"type":"string"},"scopes":{"type":"array","items":{"type":"string"}}}}}}}}}},"/v1/api-keys/{keyId}":{"patch":{"summary":"Update API key name or scopes","description":"Requires admin scope.","security":[{"bearerAuth":[]}],"parameters":[{"name":"keyId","in":"path","required":true,"schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string"},"scopes":{"type":"array","items":{"type":"string","enum":["read","send","admin","oversight"]}}}}}}},"responses":{"200":{"description":"Updated","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"string","example":"updated"}}}}}},"404":{"description":"Key not found"}}},"delete":{"summary":"Revoke an API key","description":"Requires admin scope. Returns 202 with pending_approval on first call; resend with approval_code to complete.","security":[{"bearerAuth":[]}],"parameters":[{"name":"keyId","in":"path","required":true,"schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"approval_code":{"type":"string","description":"Operator approval code (from email). Omit on first call to request approval."}}}}}},"responses":{"200":{"description":"Revoked","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"string","example":"revoked"}}}}}},"202":{"description":"Approval requested — check oversight email for code"},"403":{"description":"Invalid approval code"},"404":{"description":"Key not found or no pending approval"}}}},"/v1/billing/checkout":{"post":{"summary":"Create a Stripe checkout session for plan upgrade","description":"Requires admin scope.","security":[{"bearerAuth":[]}],"requestBody":{"content":{"application/json":{"schema":{"type":"object","required":["plan"],"properties":{"plan":{"type":"string","enum":["builder","pro","scale"]},"interval":{"type":"string","enum":["monthly","annual"],"default":"monthly"},"success_url":{"type":"string","format":"uri"},"cancel_url":{"type":"string","format":"uri"}}}}}},"responses":{"200":{"description":"Checkout session","content":{"application/json":{"schema":{"type":"object","properties":{"checkout_url":{"type":"string"},"session_id":{"type":"string"}}}}}}}}},"/v1/billing/crypto-checkout":{"post":{"summary":"Create a Coinbase Commerce checkout (crypto payment)","description":"Requires admin scope. One-time charge.","security":[{"bearerAuth":[]}],"requestBody":{"content":{"application/json":{"schema":{"type":"object","required":["plan"],"properties":{"plan":{"type":"string","enum":["builder","pro","scale"]}}}}}},"responses":{"200":{"description":"Checkout created","content":{"application/json":{"schema":{"type":"object","properties":{"checkout_url":{"type":"string"},"charge_id":{"type":"string"}}}}}}}}},"/v1/billing/stripe-webhook":{"post":{"summary":"Stripe webhook handler (public, signature-verified)"}},"/v1/billing/pricing-checkout":{"post":{"summary":"Create account + Stripe checkout in one step (public, no auth)","description":"Creates an inactive tenant, provisions a default mailbox, and returns a Stripe checkout URL. After payment, call GET /v1/billing/session-key to retrieve the API key. Honors an optional Idempotency-Key request header (UUID); the same key is forwarded to Stripe so duplicate Sessions are not created on retry.","parameters":[{"name":"Idempotency-Key","in":"header","required":false,"schema":{"type":"string","maxLength":200},"description":"Client-generated UUID. Cached for 1hr in our KV; also forwarded to Stripe (24hr server-side dedupe window)."}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PricingCheckoutRequest"}}}},"responses":{"200":{"description":"Checkout session","content":{"application/json":{"schema":{"type":"object","properties":{"checkout_url":{"type":"string","format":"uri"}}}}}},"400":{"description":"Validation error"},"409":{"description":"Operator name taken or concurrent Idempotency-Key in flight"}}}},"/v1/billing/session-key":{"get":{"summary":"Retrieve one-time API key after pricing-page checkout","description":"Public endpoint. Returns the API key stored during pricing-checkout, then deletes it. Key expires after 1 hour if not retrieved.","parameters":[{"name":"session_id","in":"query","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"API key + onboarding data","content":{"application/json":{"schema":{"type":"object","properties":{"api_key":{"type":"string"},"mailbox_id":{"type":"string","nullable":true},"mailbox_address":{"type":"string","nullable":true},"oversight_mode":{"type":"string"},"org_name":{"type":"string","nullable":true},"oversight_email":{"type":"string","nullable":true}}}}}},"404":{"description":"Session not found or expired"}}}},"/v1/billing/coinbase-webhook":{"post":{"summary":"Coinbase Commerce webhook handler (public, signature-verified)"}},"/v1/funnel/event":{"post":{"summary":"Funnel event beacon (public, no auth)","description":"Pricing page beacon hit via navigator.sendBeacon to track open/submit/error events on the signup modal. Fire-and-forget; counters are best-effort (KV is non-atomic). IP-rate-limited to 30 req/min.","requestBody":{"content":{"application/json":{"schema":{"type":"object","required":["event"],"properties":{"event":{"type":"string","enum":["pricing_modal_open_starter","pricing_modal_open_paid","pricing_modal_submit_starter_success","pricing_modal_submit_starter_error","pricing_modal_submit_paid_success","pricing_modal_submit_paid_error"]}}}}}},"responses":{"200":{"description":"Event recorded","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean","example":true}}}}}},"400":{"description":"Unknown event or invalid JSON"},"429":{"description":"Rate limited (>30 req/min/IP)"}}}},"/v1/billing/cancel":{"post":{"summary":"Cancel subscription at end of billing period","description":"Requires admin scope. Sets cancel_at_period_end on the Stripe subscription so the tenant retains access until the current billing period ends.","security":[{"bearerAuth":[]}],"responses":{"200":{"description":"Cancellation scheduled","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"string","example":"cancelling"},"cancel_at_period_end":{"type":"boolean"},"access_until":{"type":"string","format":"date-time"}}}}}},"400":{"description":"No active subscription"}}}},"/v1/billing/portal":{"post":{"summary":"Create a Stripe customer portal session","description":"Requires admin scope. Returns a URL to the Stripe-hosted billing portal for self-service invoice, payment method, and plan management.","security":[{"bearerAuth":[]}],"requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"return_url":{"type":"string","format":"uri"}}}}}},"responses":{"200":{"description":"Portal session","content":{"application/json":{"schema":{"type":"object","properties":{"portal_url":{"type":"string","format":"uri"}}}}}},"400":{"description":"No Stripe customer on file"}}}},"/v1/webhooks/postmark":{"post":{"summary":"Postmark bounce/complaint/delivery webhook handler"}},"/v1/confirm":{"get":{"summary":"Redirect to frontend confirmation page at multimail.dev/confirm"},"post":{"summary":"Activate account with confirmation code","description":"JSON response includes: status, name, oversight_mode, api_key, mailbox_id, mailbox_address, oversight_email, use_case. Browser form submissions redirect to /welcome."}},"/v1/confirm/{code}":{"get":{"summary":"Redirect to frontend confirmation page with code prefilled","parameters":[{"name":"code","in":"path","required":true,"schema":{"type":"string"}}]}},"/v1/domains":{"post":{"summary":"Add a custom domain (Pro/Scale only)","description":"Requires admin scope.","security":[{"bearerAuth":[]}],"requestBody":{"content":{"application/json":{"schema":{"type":"object","required":["domain"],"properties":{"domain":{"type":"string"}}}}}},"responses":{"201":{"description":"Domain created with DNS records to configure","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Domain"}}}},"403":{"description":"Plan restriction"}}},"get":{"summary":"List custom domains","description":"Requires admin scope.","security":[{"bearerAuth":[]}],"responses":{"200":{"description":"List of custom domains","content":{"application/json":{"schema":{"type":"object","properties":{"domains":{"type":"array","items":{"$ref":"#/components/schemas/Domain"}}}}}}}}}},"/v1/domains/{id}":{"get":{"summary":"Get custom domain detail","description":"Requires admin scope.","security":[{"bearerAuth":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Domain detail","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Domain"}}}}}},"delete":{"summary":"Delete a custom domain","description":"Requires admin scope.","security":[{"bearerAuth":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Domain deleted","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"string","example":"deleted"}}}}}}}}},"/v1/domains/{id}/verify":{"post":{"summary":"Trigger DNS verification for a custom domain","description":"Requires admin scope.","security":[{"bearerAuth":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Verification result","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DomainVerifyResult"}}}}}}},"/v1/audit-log":{"get":{"summary":"List audit log entries","description":"Returns audit log entries with cursor pagination. Requires admin scope.","security":[{"bearerAuth":[]}],"parameters":[{"name":"limit","in":"query","schema":{"type":"integer","minimum":1,"maximum":100,"default":50}},{"name":"cursor","in":"query","schema":{"type":"string"}}],"responses":{"200":{"description":"Audit log entries","content":{"application/json":{"schema":{"type":"object","properties":{"entries":{"type":"array","items":{"$ref":"#/components/schemas/AuditEntry"}},"cursor":{"type":"string","nullable":true}}}}}}}}},"/v1/suppression":{"get":{"summary":"List suppressed email addresses","description":"Returns addresses suppressed due to bounces, spam complaints, or manual unsubscribes. Requires admin scope.","security":[{"bearerAuth":[]}],"responses":{"200":{"description":"Suppression list","content":{"application/json":{"schema":{"type":"object","properties":{"suppressions":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"email_address":{"type":"string"},"reason":{"type":"string"},"mailbox_id":{"type":"string","nullable":true},"created_at":{"type":"string","format":"date-time"}}}}}}}}},"403":{"description":"Requires admin scope"}}}},"/v1/suppression/{address}":{"delete":{"summary":"Remove an address from the suppression list","description":"Allows future emails to be sent to this address again. Requires admin scope.","security":[{"bearerAuth":[]}],"parameters":[{"name":"address","in":"path","required":true,"schema":{"type":"string","format":"email"}}],"responses":{"200":{"description":"Address removed from suppression list"},"404":{"description":"Address not found in suppression list"}}}},"/v1/webhook-deliveries":{"get":{"summary":"List webhook delivery history","description":"Returns recent webhook delivery attempts. Requires admin scope.","security":[{"bearerAuth":[]}],"parameters":[{"name":"limit","in":"query","schema":{"type":"integer","minimum":1,"maximum":100,"default":50}},{"name":"cursor","in":"query","schema":{"type":"string"}}],"responses":{"200":{"description":"Webhook delivery history","content":{"application/json":{"schema":{"type":"object","properties":{"deliveries":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"event_type":{"type":"string"},"url":{"type":"string"},"status":{"type":"string","enum":["pending","delivered","failed"]},"attempts":{"type":"integer"},"response_status":{"type":"integer","nullable":true},"created_at":{"type":"string","format":"date-time"},"last_attempt_at":{"type":"string","format":"date-time","nullable":true}}}},"cursor":{"type":"string","nullable":true}}}}}},"403":{"description":"Requires admin scope"}}}},"/v1/usage":{"get":{"summary":"Get usage statistics","description":"Requires read scope. Returns usage counts for the current billing period.","security":[{"bearerAuth":[]}],"parameters":[{"name":"from","in":"query","schema":{"type":"integer"},"description":"Start time (epoch ms)"},{"name":"to","in":"query","schema":{"type":"integer"},"description":"End time (epoch ms)"},{"name":"breakdown","in":"query","schema":{"type":"string","enum":["daily"]}}],"responses":{"200":{"description":"Usage statistics","content":{"application/json":{"schema":{"oneOf":[{"$ref":"#/components/schemas/UsageSummary"},{"$ref":"#/components/schemas/UsageDaily"}]}}}}}}},"/.well-known/multimail-signing-key":{"get":{"summary":"Public signing key for identity verification","description":"Returns the ECDSA P-256 public key used to sign X-MultiMail-Identity headers.","responses":{"200":{"description":"Public key document"}}}},"/.well-known/reputation/{hash}":{"get":{"summary":"Look up agent mailbox reputation by hash","description":"Rate-limited to 10 lookups per IP per hour.","parameters":[{"name":"hash","in":"path","required":true,"schema":{"type":"string","pattern":"^[a-f0-9]{64}$"}}],"responses":{"200":{"description":"Reputation data"},"404":{"description":"No reputation data found"},"429":{"description":"Rate limit exceeded"}}}},"/v1/export":{"get":{"summary":"Bulk export all account data","description":"Requires admin scope. Rate limited to 1 request per hour.","security":[{"bearerAuth":[]}],"responses":{"200":{"description":"Full account data export"},"403":{"description":"Requires admin scope"},"429":{"description":"Export rate limited (1 per hour)"}}}},"/v1/unsubscribe/{token}":{"get":{"summary":"Render unsubscribe page (CAN-SPAM)","parameters":[{"name":"token","in":"path","required":true,"schema":{"type":"string"}}]},"post":{"summary":"Process unsubscribe request","parameters":[{"name":"token","in":"path","required":true,"schema":{"type":"string"}}]}},"/v1/approve/{token}":{"get":{"summary":"Render hosted approval page for oversight decisions","parameters":[{"name":"token","in":"path","required":true,"schema":{"type":"string"}}]},"post":{"summary":"Process approval/rejection from hosted page","parameters":[{"name":"token","in":"path","required":true,"schema":{"type":"string"}}]}},"/v1/admin/recover-key":{"post":{"summary":"Generate a new API key for a tenant (admin key recovery)","description":"Admin-only. Creates a new API key and emails it to the tenant's oversight email. Used when welcome email failed or KV expired before key retrieval.","security":[{"bearerAuth":[]}],"requestBody":{"content":{"application/json":{"schema":{"type":"object","required":["tenant_id","reason"],"properties":{"tenant_id":{"type":"string"},"reason":{"type":"string"}}}}}},"responses":{"200":{"description":"Key generated and emailed","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"key_id":{"type":"string"},"key_prefix":{"type":"string"},"api_key":{"type":"string"},"email_sent":{"type":"boolean"},"tenant_id":{"type":"string"}}}}}},"401":{"description":"Unauthorized"},"404":{"description":"Tenant not found"}}}},"/v1/openapi.json":{"get":{"summary":"This OpenAPI specification","description":"Public endpoint (no auth required). Returns this OpenAPI 3.0.3 document.","responses":{"200":{"description":"OpenAPI specification document","content":{"application/json":{"schema":{"type":"object"}}}}}}},"/health":{"get":{"summary":"Readiness health check","description":"Verifies D1 and R2 connectivity. No auth required.","responses":{"200":{"description":"All systems operational","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"string","example":"ok","enum":["ok","degraded"]}}}}}},"503":{"description":"One or more checks failed"}}}},"/.well-known/oauth-protected-resource":{"get":{"summary":"Protected resource metadata (RFC 9728)","description":"Returns metadata about MultiMail as an OAuth-protected resource, including supported scopes and authorization servers. Part of the auth.md agent registration protocol.","responses":{"200":{"description":"Protected resource metadata","content":{"application/json":{"schema":{"type":"object","properties":{"resource":{"type":"string"},"resource_name":{"type":"string"},"resource_logo_uri":{"type":"string"},"resource_documentation":{"type":"string"},"scopes_supported":{"type":"array","items":{"type":"string"}},"authorization_servers":{"type":"array","items":{"type":"string"}},"bearer_methods_supported":{"type":"array","items":{"type":"string"}}}}}}}}}},"/.well-known/oauth-authorization-server":{"get":{"summary":"Authorization server metadata with agent_auth block","description":"Returns OAuth authorization server metadata with an agent_auth extension block describing the auth.md agent registration flow.","responses":{"200":{"description":"Authorization server metadata","content":{"application/json":{"schema":{"type":"object","properties":{"issuer":{"type":"string"},"agent_auth":{"type":"object","properties":{"skill":{"type":"string"},"register_uri":{"type":"string"},"claim_uri":{"type":"string"},"identity_types_supported":{"type":"array","items":{"type":"string"}},"identity_assertion":{"type":"object"}}}}}}}}}}},"/auth.md":{"get":{"summary":"Agent registration skill document","description":"Returns a markdown document describing MultiMail's agent registration flow, trust ladder, and scope model. Used by agents following the auth.md protocol.","responses":{"200":{"description":"Markdown skill document","content":{"text/markdown":{"schema":{"type":"string"}}}}}}},"/agent/auth":{"post":{"summary":"Register agent via auth.md protocol","description":"Initiates agent registration using verified_email identity assertion. Sends a 6-digit OTP to the provided email and returns a claim_token for completing the registration.","requestBody":{"content":{"application/json":{"schema":{"type":"object","required":["type","assertion_type","assertion"],"properties":{"type":{"type":"string","enum":["identity_assertion"],"description":"Registration type. Only identity_assertion is supported."},"assertion_type":{"type":"string","enum":["verified_email"],"description":"Assertion type. Only verified_email is supported."},"assertion":{"type":"object","required":["email"],"properties":{"email":{"type":"string","format":"email","maxLength":320}}},"requested_credential_type":{"type":"string","enum":["api_key"],"description":"Credential type. Only api_key is supported."},"requested_oversight_mode":{"type":"string","enum":["gated_all","gated_send","monitored"],"description":"Optional initial oversight mode for the mailbox."},"operator_name":{"type":"string","maxLength":100,"description":"Optional operator name. Derived from email local part if absent."}}}}}},"responses":{"200":{"description":"Registration initiated","content":{"application/json":{"schema":{"type":"object","properties":{"registration_id":{"type":"string"},"registration_type":{"type":"string"},"claim_url":{"type":"string"},"claim_token":{"type":"string"},"claim_token_expires":{"type":"string","format":"date-time"},"post_claim_scopes":{"type":"array","items":{"type":"string"}}}}}}},"400":{"description":"Invalid request (unsupported type, invalid email, etc.)"},"429":{"description":"Rate limited"}}}},"/agent/auth/claim/view":{"get":{"summary":"View OTP verification code","description":"Human-facing page that displays the 6-digit OTP for agent registration. Linked from the verification email.","parameters":[{"name":"token","in":"query","required":true,"schema":{"type":"string"},"description":"Claim view token from verification email"}],"responses":{"200":{"description":"HTML page displaying the OTP","content":{"text/html":{"schema":{"type":"string"}}}}}}},"/agent/auth/claim/complete":{"post":{"summary":"Complete agent registration claim","description":"Completes the auth.md registration by validating the claim_token and OTP. On success, atomically creates the tenant account and returns API credentials.","requestBody":{"content":{"application/json":{"schema":{"type":"object","required":["claim_token","otp"],"properties":{"claim_token":{"type":"string","maxLength":512,"description":"Claim token returned from POST /agent/auth"},"otp":{"type":"string","pattern":"^\\d{6}$","description":"6-digit OTP from verification email"}}}}}},"responses":{"200":{"description":"Claim successful, credentials returned","content":{"application/json":{"schema":{"type":"object","properties":{"registration_id":{"type":"string"},"status":{"type":"string","enum":["claimed"]},"credential_type":{"type":"string","enum":["api_key"]},"credential":{"type":"string"},"credential_expires":{"type":"string","nullable":true},"scopes":{"type":"array","items":{"type":"string"}}}}}}},"400":{"description":"Invalid claim token, wrong OTP, or expired"},"409":{"description":"Already claimed"},"429":{"description":"Too many attempts"},"503":{"description":"Service unavailable (KV failure, fail-closed)"}}}}},"components":{"securitySchemes":{"bearerAuth":{"type":"http","scheme":"bearer","description":"API key (mm_live_...). Keys have scopes: read (list/read emails, mailboxes, contacts), send (send/reply/cancel emails), admin (manage mailboxes, API keys, billing, account), oversight (approve/reject pending emails)."}},"schemas":{"SignupRequest":{"type":"object","required":["operator_name","accepted_tos","accepted_operator_agreement","accepted_anti_spam_policy","oversight_email","pow_solution"],"properties":{"operator_name":{"type":"string","description":"Company or individual name. Included in identity headers and signature block."},"payment_method":{"type":"string","enum":["stripe","crypto"],"description":"Signup intent source. Pricing page starter flow sends stripe."},"accepted_tos":{"type":"boolean","description":"Must be true."},"accepted_operator_agreement":{"type":"boolean","description":"Must be true."},"accepted_anti_spam_policy":{"type":"boolean","description":"Must be true."},"email_use_type":{"type":"string","enum":["transactional","marketing"],"description":"Only transactional is accepted today."},"oversight_email":{"type":"string","format":"email"},"slug":{"type":"string","description":"URL-safe slug. Auto-generated from operator_name if omitted."},"physical_address":{"type":"string"},"use_case":{"type":"string"},"oversight_mode":{"type":"string","enum":["gated_all","gated_send","monitored"],"description":"Optional initial mailbox oversight mode. Unsafe modes are rejected."},"cf_challenge_response":{"type":"string","maxLength":4096,"description":"Preferred browser Turnstile token field. Mirrors the hidden cf-turnstile-response input value. maxLength keeps API Shield's auto-learned body-size cap above the ~1.6KB Turnstile token (see lesson #18)."},"turnstile_token":{"type":"string","maxLength":4096,"deprecated":true,"description":"Legacy Turnstile token field. Accepted for backward compatibility. maxLength matches cf_challenge_response — see lesson #18."},"fingerprint":{"type":"string","maxLength":256,"description":"Optional browser fingerprint used for signup throttling."},"form_open_at":{"type":"integer","format":"int64","description":"Epoch milliseconds when the signup modal opened."},"pow_solution":{"type":"object","required":["algorithm","challenge","number","salt","signature"],"description":"Solved ALTCHA proof-of-work challenge from POST /v1/account/challenge.","properties":{"algorithm":{"type":"string","example":"SHA-256"},"challenge":{"type":"string","description":"Challenge hash from the challenge endpoint"},"number":{"type":"integer","description":"Solved number N where SHA-256(salt + N) matches challenge"},"salt":{"type":"string","description":"Salt from the challenge (echo back unchanged)"},"signature":{"type":"string","description":"Signature from the challenge (echo back unchanged)"}}},"attribution":{"$ref":"#/components/schemas/AttributionPayload"},"agent_did":{"type":"string","maxLength":256,"description":"Optional Ed25519 did:key to bind to this account (DID bridge). Provide with did_challenge_id + did_signature; all three together or none."},"did_challenge_id":{"type":"string","maxLength":64,"description":"Opaque challenge_id from POST /v1/account/did-challenge."},"did_signature":{"type":"string","maxLength":96,"description":"base64url Ed25519 signature over the account-bound binding payload (64 raw bytes → ≤88 base64url chars; maxLength 96 keeps the API-Shield body cap above it — lesson #18)."}}},"PricingCheckoutRequest":{"type":"object","description":"Paid signup + Stripe checkout body for POST /v1/billing/pricing-checkout. Named (rather than inline) so scripts/verify-api-shield-sync.sh can confirm the Turnstile maxLength cap, mirroring SignupRequest (see lesson #18).","required":["operator_name","oversight_email","plan","accepted_tos","accepted_operator_agreement","accepted_anti_spam_policy"],"properties":{"operator_name":{"type":"string"},"oversight_email":{"type":"string","format":"email"},"plan":{"type":"string","enum":["builder","pro","scale"]},"interval":{"type":"string","enum":["monthly","annual"],"default":"monthly"},"accepted_tos":{"type":"boolean"},"accepted_operator_agreement":{"type":"boolean"},"accepted_anti_spam_policy":{"type":"boolean"},"cf_challenge_response":{"type":"string","maxLength":4096,"description":"Preferred browser Turnstile token field (mirrors SignupRequest). Not read by this endpoint — Stripe handles fraud on paid checkout — but declared so API Shield's body-size cap stays above the ~1.6KB token if a browser sends one (see lesson #18)."},"turnstile_token":{"type":"string","maxLength":4096,"deprecated":true,"description":"Legacy Turnstile token field. maxLength keeps API Shield's auto-learned body-size cap above the ~1.6KB token (see lesson #18)."},"use_case":{"type":"string"},"oversight_mode":{"type":"string","enum":["gated_all","gated_send","monitored"]},"attribution":{"$ref":"#/components/schemas/AttributionPayload"}}},"AttributionPayload":{"type":"object","description":"Optional client-captured conversion attribution (Phase 2 of plan 2026-04-27-003). The browser persists this in localStorage.mm_attribution_v1 and forwards it on signup. Server-side validator at src/lib/attribution.ts is the source of truth — bad shapes are silently dropped, NEVER reject the signup. additionalProperties:false on this object AND its sub-objects keeps the API Shield body-size cap honest even if the per-operation mitigation pin in scripts/sync-api-shield.sh is ever undone: an attacker cannot pad the body with arbitrary unknown keys to bypass the per-field maxLengths (adversary review iter1 finding ATTR-1).","additionalProperties":false,"properties":{"first":{"$ref":"#/components/schemas/AttributionVisit"},"last":{"$ref":"#/components/schemas/AttributionVisit"},"visits":{"type":"integer","format":"int32","minimum":1,"maximum":1000000,"description":"Visit counter, incremented client-side. Server validator coerces missing/zero/negative values to 1."},"version":{"type":"integer","format":"int32","enum":[1],"description":"Schema version. Only 1 is currently accepted; future bumps will rotate the localStorage key client-side and add new branches server-side."}}},"AttributionVisit":{"type":"object","description":"A single landing event — first or last touch.","additionalProperties":false,"properties":{"ts":{"type":"integer","format":"int64","description":"Epoch milliseconds at landing."},"landing_path":{"type":"string","maxLength":256,"description":"Pathname of the landing page (no host)."},"params":{"type":"object","description":"Tracked URL params present on the landing page. All keys optional; unknown keys are dropped server-side. additionalProperties:false enforces the allowlist at the schema layer too (adversary review iter1 ATTR-1).","additionalProperties":false,"properties":{"utm_source":{"type":"string","maxLength":256},"utm_medium":{"type":"string","maxLength":256},"utm_campaign":{"type":"string","maxLength":256},"utm_content":{"type":"string","maxLength":256},"utm_term":{"type":"string","maxLength":256},"gclid":{"type":"string","maxLength":256},"fbclid":{"type":"string","maxLength":256},"msclkid":{"type":"string","maxLength":256}}}}},"SignupResponse":{"type":"object","description":"Always returns the same response for privacy (anti-enumeration). Does not confirm whether the account was actually created.","properties":{"status":{"type":"string","example":"confirmation_sent"},"message":{"type":"string","example":"Check your email for the activation code."}}},"CreateMailboxRequest":{"type":"object","required":["address_local"],"properties":{"address_local":{"type":"string","description":"Local part (e.g. 'inbox') or full address on custom domain (e.g. 'inbox@custom.com')"},"display_name":{"type":"string"},"oversight_mode":{"type":"string","description":"Ignored — new mailboxes always start at gated_send. Use the upgrade flow to change.","default":"gated_send"},"webhook_url":{"type":"string","format":"uri"},"approval_code":{"type":"string","description":"Operator approval code (from the oversight email). Omit on the first call to request approval (202); resubmit with it to complete creation."}}},"UpdateMailboxRequest":{"type":"object","properties":{"display_name":{"type":"string"},"oversight_mode":{"type":"string","enum":["read_only","gated_all","gated_send","monitored","autonomous"],"description":"Downgrade only. To upgrade, use POST /v1/mailboxes/{id}/request-upgrade."},"auto_cc":{"type":"string","format":"email","nullable":true,"description":"Operator-controlled. Changing this requires operator approval via the two-step approval_code flow."},"auto_bcc":{"type":"string","format":"email","nullable":true,"description":"Operator-controlled. Changing this — including clearing to null — requires operator approval via the two-step approval_code flow."},"forward_inbound":{"type":"boolean"},"signature_block":{"type":"string","maxLength":200,"nullable":true},"ai_disclosure":{"type":"boolean"},"approval_code":{"type":"string","description":"Operator approval code for gated changes (auto_cc/auto_bcc). Omit on the first call to receive a code at the oversight email; resubmit with it to complete. webhook_url is NOT settable here — webhooks are operator-only via create_webhook."}}},"Mailbox":{"type":"object","properties":{"id":{"type":"string"},"tenant_id":{"type":"string"},"address":{"type":"string"},"display_name":{"type":"string","nullable":true},"oversight_mode":{"type":"string"},"auto_cc":{"type":"string","nullable":true},"auto_bcc":{"type":"string","nullable":true},"forward_inbound":{"type":"integer"},"webhook_url":{"type":"string","nullable":true},"oversight_webhook_url":{"type":"string","nullable":true},"signature_block":{"type":"string","nullable":true},"created_at":{"type":"integer","description":"Epoch ms"},"is_active":{"type":"integer"},"default_gate_timing":{"type":"string","enum":["gate_first","schedule_first"],"nullable":true,"description":"Default approval timing for scheduled sends: gate_first approves before scheduling, schedule_first schedules then approves at delivery."},"scheduling_enabled":{"type":"integer","description":"1 if send_at scheduling is enabled for this mailbox"},"mcp_configured":{"type":"integer","description":"1 once an MCP client has been configured for this mailbox (guided-setup progress flag)"},"ai_disclosure":{"type":"integer","description":"1 if outbound emails carry the AI-agent disclosure block"}}},"SendEmailRequest":{"type":"object","required":["to","subject","markdown"],"properties":{"to":{"type":"array","items":{"type":"string","format":"email"},"maxItems":50},"cc":{"type":"array","items":{"type":"string","format":"email"}},"bcc":{"type":"array","items":{"type":"string","format":"email"}},"subject":{"type":"string","maxLength":998},"markdown":{"type":"string","maxLength":262144,"description":"Email body in markdown. Converted to HTML."},"attachments":{"type":"array","items":{"type":"object","required":["name","content_base64","content_type"],"properties":{"name":{"type":"string"},"content_base64":{"type":"string"},"content_type":{"type":"string"}}}},"idempotency_key":{"type":"string","description":"Dedup key (24h TTL). If resent within TTL, returns the original email ID."},"send_at":{"type":"string","description":"Schedule delivery for a future time. ISO 8601 UTC (must end with Z). Example: 2026-03-15T14:00:00Z"},"gate_timing":{"type":"string","enum":["gate_first","schedule_first"],"description":"Override mailbox default: gate_first approves before scheduling, schedule_first schedules then approves on delivery"},"ucan":{"type":"string","maxLength":11008,"description":"Optional UCAN (base64url) delegating email-send authority from the agent's did:key to MultiMail. Relayed verbatim in the X-Agent-Identity header; MultiMail does not sign or verify it (the recipient does). Only meaningful when the account has a bound agent DID."}}},"ReplyRequest":{"type":"object","required":["markdown"],"properties":{"markdown":{"type":"string","maxLength":262144},"cc":{"type":"array","items":{"type":"string","format":"email"}},"bcc":{"type":"array","items":{"type":"string","format":"email"}},"attachments":{"type":"array","items":{"type":"object","required":["name","content_base64","content_type"],"properties":{"name":{"type":"string"},"content_base64":{"type":"string"},"content_type":{"type":"string"}}}},"idempotency_key":{"type":"string","description":"Dedup key (24h TTL)"},"ucan":{"type":"string","maxLength":11008,"description":"Optional UCAN (base64url) delegating email-send authority from the agent's did:key to MultiMail. Relayed verbatim in the X-Agent-Identity header; MultiMail does not sign or verify it (the recipient does). Only meaningful when the account has a bound agent DID."}}},"EmailListResponse":{"type":"object","properties":{"emails":{"type":"array","items":{"$ref":"#/components/schemas/EmailSummary"}},"cursor":{"type":"string","nullable":true}}},"EmailSummary":{"type":"object","properties":{"id":{"type":"string"},"mailbox_id":{"type":"string","description":"Present on tenant-wide listings (GET /v1/emails); omitted on per-mailbox listings where the mailbox is implied by the path."},"from":{"type":"string"},"to":{"type":"array","items":{"type":"string"}},"subject":{"type":"string"},"direction":{"type":"string","enum":["inbound","outbound"]},"status":{"type":"string","enum":["unread","read","archived","deleted","pending_send_approval","pending_inbound_approval","rejected","cancelled","sending","send_failed","scheduled","spam_flagged","spam_quarantined"]},"spam_score":{"type":"integer","nullable":true},"spam_verdict":{"type":"string","enum":["clean","flagged","quarantined"],"nullable":true},"received_at":{"type":"string","format":"date-time"},"thread_id":{"type":"string","nullable":true},"has_attachments":{"type":"boolean"},"delivered_at":{"type":"string","format":"date-time","nullable":true},"bounced_at":{"type":"string","format":"date-time","nullable":true},"bounce_type":{"type":"string","nullable":true}}},"EmailResponse":{"type":"object","properties":{"id":{"type":"string"},"from":{"type":"string"},"to":{"type":"array","items":{"type":"string"}},"cc":{"type":"array","items":{"type":"string"}},"subject":{"type":"string"},"received_at":{"type":"string","format":"date-time"},"thread_id":{"type":"string","nullable":true},"markdown":{"type":"string"},"status":{"type":"string","enum":["unread","read","archived","deleted","pending_send_approval","pending_inbound_approval","rejected","cancelled","sending","send_failed","scheduled","spam_flagged","spam_quarantined"]},"has_attachments":{"type":"boolean"},"attachments":{"type":"array","items":{"type":"object","properties":{"name":{"type":"string"},"size":{"type":"integer"},"content_type":{"type":"string"}}}},"delivered_at":{"type":"string","format":"date-time","nullable":true},"bounced_at":{"type":"string","format":"date-time","nullable":true},"bounce_type":{"type":"string","nullable":true},"approved_at":{"type":"string","format":"date-time","nullable":true},"approved_by":{"type":"string","nullable":true},"spam_score":{"type":"integer","nullable":true},"spam_verdict":{"type":"string","enum":["clean","flagged","quarantined"],"nullable":true},"tags":{"type":"object","additionalProperties":{"type":"string"}}}},"ThreadResponse":{"type":"object","properties":{"thread_id":{"type":"string"},"message_count":{"type":"integer"},"participants":{"type":"array","items":{"type":"string"}},"last_activity":{"type":"string","format":"date-time"},"has_unanswered_inbound":{"type":"boolean"},"emails":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"from":{"type":"string"},"to":{"type":"array","items":{"type":"string"}},"subject":{"type":"string"},"direction":{"type":"string","enum":["inbound","outbound"]},"status":{"type":"string"},"received_at":{"type":"string","format":"date-time"},"has_attachments":{"type":"boolean"},"delivered_at":{"type":"string","format":"date-time","nullable":true},"bounced_at":{"type":"string","format":"date-time","nullable":true}}}}}},"Contact":{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"},"email":{"type":"string","format":"email"},"tags":{"type":"array","items":{"type":"string"}},"created_at":{"type":"integer","description":"Epoch ms"}}},"WebhookSubscription":{"type":"object","properties":{"id":{"type":"string"},"url":{"type":"string"},"events":{"type":"array","items":{"type":"string"}},"mailbox_id":{"type":"string","nullable":true},"signing_secret":{"type":"string","description":"HMAC signing secret (whsec_...)"},"created_at":{"type":"string","format":"date-time"}}},"ApiKeyInfo":{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"},"prefix":{"type":"string","description":"Key prefix (e.g. mm_live_abc...)"},"scopes":{"type":"array","items":{"type":"string","enum":["read","send","admin","oversight"]}},"created_at":{"type":"string","format":"date-time"},"last_used_at":{"type":"string","format":"date-time","nullable":true},"is_active":{"type":"boolean"}}},"AccountInfo":{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"},"slug":{"type":"string"},"plan":{"type":"string","enum":["starter","builder","pro","scale"]},"oversight_email":{"type":"string","format":"email"},"physical_address":{"type":"string","nullable":true},"monthly_email_quota":{"type":"integer"},"emails_sent_this_month":{"type":"integer"},"max_mailboxes":{"type":"integer"},"storage_used_bytes":{"type":"integer"},"max_storage_bytes":{"type":"integer"},"is_active":{"type":"boolean"},"sending_disabled":{"type":"boolean"},"sending_disabled_reason":{"type":"string","nullable":true}}},"UsageSummary":{"type":"object","properties":{"from":{"type":"integer","description":"Start time (epoch ms)"},"to":{"type":"integer","description":"End time (epoch ms)"},"emails_sent":{"type":"integer"},"emails_received":{"type":"integer"},"api_calls":{"type":"integer"},"oversight_actions":{"type":"integer"}}},"UsageDaily":{"type":"object","properties":{"from":{"type":"integer"},"to":{"type":"integer"},"breakdown":{"type":"string","enum":["daily"]},"data":{"type":"array","items":{"type":"object","properties":{"date":{"type":"string"},"event_type":{"type":"string"},"count":{"type":"integer"}}}}}},"Domain":{"type":"object","properties":{"id":{"type":"string"},"domain":{"type":"string"},"status":{"type":"string","enum":["pending","active"]},"dkim_verified":{"type":"boolean"},"spf_verified":{"type":"boolean"},"mx_verified":{"type":"boolean"},"return_path_verified":{"type":"boolean"},"dns_records":{"type":"object","nullable":true},"created_at":{"type":"string","format":"date-time"},"verified_at":{"type":"string","format":"date-time","nullable":true}}},"DomainVerifyResult":{"type":"object","properties":{"id":{"type":"string"},"domain":{"type":"string"},"status":{"type":"string","enum":["pending","active"]},"dkim_verified":{"type":"boolean"},"spf_verified":{"type":"boolean"},"mx_verified":{"type":"boolean"},"return_path_verified":{"type":"boolean"},"verified_at":{"type":"string","format":"date-time","nullable":true}}},"AuditEntry":{"type":"object","properties":{"id":{"type":"string"},"action":{"type":"string"},"resource_type":{"type":"string"},"resource_id":{"type":"string"},"actor_key_id":{"type":"string"},"metadata":{"type":"object","nullable":true},"created_at":{"type":"string","format":"date-time"}}},"Error":{"type":"object","properties":{"error":{"type":"string"},"code":{"type":"string","description":"Optional machine-readable error code (e.g. ACCOUNT_INACTIVE on 403 for suspended tenants, SANCTIONED_REGION on 451)."},"retry_after":{"type":"integer","description":"Optional. Seconds to wait before retrying — present on rate-limit (429) responses alongside the Retry-After header."}}}}}}