{"openapi":"3.1.0","info":{"title":"MCPmailer API","version":"1.0.0","description":"Email infrastructure for AI agents. Every agent is an identity with its own mailbox, plus shared contacts, notes, and an encrypted vault. Everything here is also available as MCP tools at https://connect.mcpmailer.com/mcp.","contact":{"name":"MCPmailer support","email":"support@mcpmailer.com"}},"servers":[{"url":"https://mcpmailer.com"}],"x-rate-limit":{"description":"300 requests per minute per API key, 30 per minute per IP without one. Responses carry x-ratelimit-limit and, where available, x-ratelimit-remaining and x-ratelimit-reset. Any endpoint can return 429 with retry-after in seconds.","perKeyPerMinute":300,"anonymousPerMinutePerIp":30},"security":[{"apiKey":[]}],"tags":[{"name":"Messages","description":"Sending, reading, forwarding, attachments"},{"name":"Threads","description":"Whole conversations"},{"name":"Identity","description":"The agent behind the key, and its inbound filters"},{"name":"Contacts","description":"Workspace address book and remembered context"},{"name":"Notes","description":"Shared workspace context"},{"name":"Knowledge","description":"What agents know about the company: brief, crawled sites, pages, text"},{"name":"Secrets","description":"Vault credentials, encrypted at rest"},{"name":"Mailboxes","description":"The inboxes in this workspace"},{"name":"Domains","description":"Sending domains and the DNS records they need"},{"name":"Webhooks","description":"Signed delivery of inbound and delivery events"},{"name":"Signup","description":"Agent self-provisioning"}],"paths":{"/v1/messages":{"get":{"tags":["Messages"],"summary":"List messages","parameters":[{"name":"unread_only","in":"query","schema":{"type":"boolean"}},{"name":"thread_id","in":"query","schema":{"type":"string","format":"uuid"}},{"name":"include_archived","in":"query","schema":{"type":"boolean","default":false},"description":"Archived mail is excluded by default"},{"name":"limit","in":"query","schema":{"type":"integer","maximum":100,"default":20}}],"responses":{"200":{"description":"Messages, newest first","content":{"application/json":{"schema":{"type":"object","properties":{"messages":{"type":"array","items":{"$ref":"#/components/schemas/Message"}}}}}}},"401":{"description":"Structured error with an actionable reason","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"post":{"tags":["Messages"],"summary":"Send an email","description":"A send referencing a message you received (reply_to_message_id) is a reply: generous quotas, threaded. Anything else is a cold send, which is rate-limited and watched by the duplicate-content and velocity tripwires. Agents send their own mail by default, including cold; an agent that has been set to hold answers 202 with a pending_id instead, which means a person is reading it first.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["to","subject","body"],"properties":{"to":{"type":"array","items":{"type":"string","format":"email"},"minItems":1,"maxItems":5},"cc":{"type":"array","items":{"type":"string","format":"email"},"maxItems":5},"subject":{"type":"string"},"body":{"type":"string","description":"Markdown or plain text"},"reply_to_message_id":{"type":"string","format":"uuid"},"attachments":{"type":"array","items":{"type":"object","required":["filename","content_base64"],"properties":{"filename":{"type":"string"},"content_type":{"type":"string","default":"application/octet-stream"},"content_base64":{"type":"string","description":"base64 bytes, no data: prefix"}}},"maxItems":10},"track_opens":{"type":"boolean"},"send_at":{"type":"string","format":"date-time","description":"Send it then rather than now, up to 30 days out. Answers 202 with a scheduled_id. Nothing is checked at booking time: quotas, suppression, outbound policy and approval all apply at the moment it sends, which is why the outcome lives at /v1/messages/scheduled/{id} rather than in this reply."}}}}}},"responses":{"200":{"description":"Sent","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SendResult"}}}},"202":{"description":"Held for a person to review, or booked for later. Accepted, not refused: do not retry.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SendResult"}}}},"401":{"description":"Structured error with an actionable reason","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"Rejected with a reason","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SendResult"}}}}}}},"/v1/messages/pending":{"get":{"tags":["Messages"],"summary":"List messages waiting for a person","description":"Everything this agent has written that is held for review. Only its own: a key belongs to one agent, and unsent mail is the most private thing in an account.","parameters":[{"name":"limit","in":"query","schema":{"type":"integer","maximum":100,"default":20}}],"responses":{"200":{"description":"Held messages, newest first","content":{"application/json":{"schema":{"type":"object","properties":{"pending":{"type":"array","items":{"$ref":"#/components/schemas/PendingSend"}}}}}}},"401":{"description":"Structured error with an actionable reason","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/messages/pending/{id}":{"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"get":{"tags":["Messages"],"summary":"What a person decided about a held message","description":"The other half of a 202 from a send. Poll this, or subscribe to the approval.approved, approval.rejected and approval.expired webhook events and be told instead. A rejection carries decision_note, the reviewer's own words, which is worth reading before writing a replacement.","responses":{"200":{"description":"The decision, if there is one","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PendingOutcome"}}}},"401":{"description":"Structured error with an actionable reason","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Structured error with an actionable reason","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/messages/scheduled":{"get":{"tags":["Messages"],"summary":"List sends booked for later","description":"Only what has not gone yet. A scheduled message that sent is an ordinary sent email and lives in /v1/messages, where every other one does.","parameters":[{"name":"limit","in":"query","schema":{"type":"integer","maximum":100,"default":20}}],"responses":{"200":{"description":"Bookings, soonest first","content":{"application/json":{"schema":{"type":"object","properties":{"scheduled":{"type":"array","items":{"$ref":"#/components/schemas/ScheduledSend"}}}}}}},"401":{"description":"Structured error with an actionable reason","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/messages/scheduled/{id}":{"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"get":{"tags":["Messages"],"summary":"What became of a booking","description":"Whether or not its time has come. A booking the mailbox held for review carries a pending_id: follow it to /v1/messages/pending/{id}, because calling that sent or failed would say something untrue about mail nobody has released.","responses":{"200":{"description":"The booking","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ScheduledOutcome"}}}},"401":{"description":"Structured error with an actionable reason","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Structured error with an actionable reason","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"delete":{"tags":["Messages"],"summary":"Call a booking off","description":"409 rather than 404 when it is too late: the row exists and the request was reasonable, the message is simply already on its way. The status comes back with it, so the caller knows whether to look for a sent message, a held one, or a refusal.","responses":{"200":{"description":"Cancelled","content":{"application/json":{"schema":{"type":"object","properties":{"scheduled_id":{"type":"string","format":"uuid"},"cancelled":{"type":"boolean"}}}}}},"401":{"description":"Structured error with an actionable reason","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Structured error with an actionable reason","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"Already on its way","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ScheduledOutcome"}}}}}}},"/v1/messages/{id}":{"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"get":{"tags":["Messages"],"summary":"Read a message and mark it read","responses":{"200":{"description":"The full message","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Message"}}}},"404":{"description":"Structured error with an actionable reason","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/messages/{id}/archive":{"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"post":{"tags":["Messages"],"summary":"Archive, restore, or mark unread","description":"An empty body archives the message. Send {\"archived\": false} to restore it, or {\"unread\": true} to put it back in the unread pile for a human.","requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","properties":{"archived":{"type":"boolean"},"unread":{"type":"boolean"}}}}}},"responses":{"200":{"description":"Updated flags","content":{"application/json":{"schema":{"type":"object"}}}},"404":{"description":"Structured error with an actionable reason","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/messages/{id}/reply-all":{"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"post":{"tags":["Messages"],"summary":"Reply keeping every recipient","description":"Original sender goes in To, remaining recipients in Cc, your own address is dropped.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["body"],"properties":{"body":{"type":"string"},"attachments":{"type":"array","items":{"type":"object","required":["filename","content_base64"],"properties":{"filename":{"type":"string"},"content_type":{"type":"string","default":"application/octet-stream"},"content_base64":{"type":"string","description":"base64 bytes, no data: prefix"}}}},"track_opens":{"type":"boolean"}}}}}},"responses":{"200":{"description":"Sent","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SendResult"}}}},"202":{"description":"Held for a person to review. Queued, not refused: do not retry.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SendResult"}}}},"404":{"description":"Structured error with an actionable reason","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"Structured error with an actionable reason","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/messages/{id}/forward":{"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"post":{"tags":["Messages"],"summary":"Forward a message","description":"inline quotes the original under your note. wrapped attaches the untouched original as message/rfc822, which requires raw MIME and so only works for received mail.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["to"],"properties":{"to":{"type":"array","items":{"type":"string","format":"email"},"minItems":1,"maxItems":5},"body":{"type":"string"},"mode":{"type":"string","enum":["inline","wrapped"],"default":"inline"}}}}}},"responses":{"200":{"description":"Sent","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SendResult"}}}},"202":{"description":"Held for a person to review. Queued, not refused: do not retry.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SendResult"}}}},"404":{"description":"Structured error with an actionable reason","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"Structured error with an actionable reason","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/messages/{id}/attachments/{filename}":{"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}},{"name":"filename","in":"path","required":true,"schema":{"type":"string"}}],"get":{"tags":["Messages"],"summary":"Download an attachment","responses":{"200":{"description":"Raw bytes with the original content type","content":{"*/*":{"schema":{"type":"string","format":"binary"}}}},"404":{"description":"Structured error with an actionable reason","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/threads":{"get":{"tags":["Threads"],"summary":"List conversations","parameters":[{"name":"limit","in":"query","schema":{"type":"integer","maximum":100,"default":20}}],"responses":{"200":{"description":"Threads, most recent first","content":{"application/json":{"schema":{"type":"object"}}}},"401":{"description":"Structured error with an actionable reason","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/threads/{id}":{"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"get":{"tags":["Threads"],"summary":"Load a whole conversation","description":"Every message in the thread, oldest first, so an agent has full context before replying.","responses":{"200":{"description":"The conversation","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Thread"}}}},"404":{"description":"Structured error with an actionable reason","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/search":{"get":{"tags":["Messages"],"summary":"Full-text search the mailbox","parameters":[{"name":"q","in":"query","required":true,"schema":{"type":"string"}},{"name":"limit","in":"query","schema":{"type":"integer","maximum":50,"default":20}}],"responses":{"200":{"description":"Ranked hits with excerpts","content":{"application/json":{"schema":{"type":"object"}}}},"401":{"description":"Structured error with an actionable reason","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/identity":{"get":{"tags":["Identity"],"summary":"The agent behind this key","responses":{"200":{"description":"Identity with filter rules","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Identity"}}}},"404":{"description":"Structured error with an actionable reason","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"patch":{"tags":["Identity"],"summary":"Update handle, presentation, or filter mode","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"handle":{"type":"string","pattern":"^[a-z0-9]([a-z0-9]|-){1,61}[a-z0-9]$"},"display_name":{"type":["string","null"]},"description":{"type":["string","null"]},"filter_mode":{"type":"string","enum":["blacklist","whitelist"]}}}}}},"responses":{"200":{"description":"Updated","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Identity"}}}},"400":{"description":"Structured error with an actionable reason","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"Structured error with an actionable reason","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/identity/rules":{"get":{"tags":["Identity"],"summary":"List inbound filter rules","responses":{"200":{"description":"Rules","content":{"application/json":{"schema":{"type":"object"}}}},"404":{"description":"Structured error with an actionable reason","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"post":{"tags":["Identity"],"summary":"Allow or block a sender","description":"An exact_email rule overrides a domain rule for the same sender.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["match","value","action"],"properties":{"match":{"type":"string","enum":["exact_email","domain"]},"value":{"type":"string"},"action":{"type":"string","enum":["allow","block"]}}}}}},"responses":{"201":{"description":"Stored","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MailRule"}}}},"400":{"description":"Structured error with an actionable reason","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/identity/rules/{id}":{"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"delete":{"tags":["Identity"],"summary":"Remove a filter rule","responses":{"200":{"description":"Deleted","content":{"application/json":{"schema":{"type":"object"}}}},"404":{"description":"Structured error with an actionable reason","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/contacts":{"get":{"tags":["Contacts"],"summary":"List, search, or reverse-lookup contacts","description":"Send Accept: text/vcard to export the entire address book as one .vcf stream instead of JSON.","parameters":[{"name":"q","in":"query","schema":{"type":"string"},"description":"Full-text search"},{"name":"lookup","in":"query","schema":{"type":"string"},"description":"Email or phone; exact match first, then partial"},{"name":"limit","in":"query","schema":{"type":"integer","maximum":100,"default":25}}],"responses":{"200":{"description":"Matching contacts, or the whole book as vCards","content":{"application/json":{"schema":{"type":"object","properties":{"contacts":{"type":"array","items":{"$ref":"#/components/schemas/Contact"}}}}},"text/vcard":{"schema":{"type":"string"}}}}}},"post":{"tags":["Contacts"],"summary":"Create a contact, or bulk import vCards","description":"Send vcard to import up to 1000 RFC 6350 cards; failures are isolated per card.","requestBody":{"required":true,"content":{"application/json":{"schema":{"oneOf":[{"$ref":"#/components/schemas/ContactInput"},{"$ref":"#/components/schemas/VCardImport"}]}}}},"responses":{"201":{"description":"Created or imported","content":{"application/json":{"schema":{"type":"object"}}}},"400":{"description":"Structured error with an actionable reason","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/contacts/duplicates":{"get":{"tags":["Contacts"],"summary":"Contacts that look like the same person","description":"Pairs sharing an email or phone number, or carrying an identical name.","parameters":[{"name":"limit","in":"query","schema":{"type":"integer","maximum":50,"default":25}}],"responses":{"200":{"description":"Candidate pairs","content":{"application/json":{"schema":{"type":"object"}}}}}},"post":{"tags":["Contacts"],"summary":"Merge two contacts","description":"The survivor keeps its own fields and gains the loser channels, remembered facts, domains, addresses, and notes. The loser is deleted.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["survivor_id","loser_id"],"properties":{"survivor_id":{"type":"string","format":"uuid"},"loser_id":{"type":"string","format":"uuid"}}}}}},"responses":{"200":{"description":"The surviving contact","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Contact"}}}},"400":{"description":"Structured error with an actionable reason","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Structured error with an actionable reason","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/contacts/{id}":{"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"get":{"tags":["Contacts"],"summary":"Read a contact","description":"Send Accept: text/vcard to get an RFC 6350 card instead of JSON.","responses":{"200":{"description":"The contact","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Contact"}},"text/vcard":{"schema":{"type":"string"}}}},"404":{"description":"Structured error with an actionable reason","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"patch":{"tags":["Contacts"],"summary":"Update a contact","description":"Merge patch: omitted fields keep their value; passing channels replaces the whole list.","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ContactInput"}}}},"responses":{"200":{"description":"Updated","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Contact"}}}},"404":{"description":"Structured error with an actionable reason","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"post":{"tags":["Contacts"],"summary":"Remember something about a contact","description":"The fact surfaces on every later lookup of this contact, including by other agents.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["fact"],"properties":{"fact":{"type":"string"},"message_id":{"type":"string","format":"uuid"}}}}}},"responses":{"201":{"description":"Stored","content":{"application/json":{"schema":{"type":"object"}}}},"404":{"description":"Structured error with an actionable reason","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"delete":{"tags":["Contacts"],"summary":"Delete a contact and its memories","responses":{"200":{"description":"Deleted","content":{"application/json":{"schema":{"type":"object"}}}},"404":{"description":"Structured error with an actionable reason","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/contacts/import":{"post":{"tags":["Contacts"],"summary":"Import pasted CSV or address lines, optionally onto a list","description":"A CSV with a header row (email, first name, last name, name, company, title, phone, notes), a headerless CSV, or one address per line. A row whose email already belongs to a contact reuses that contact, so one person can sit on several lists. Also accepts the raw text as text/csv with ?list_name=.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["csv"],"properties":{"csv":{"type":"string"},"list_name":{"type":"string","description":"Created if it does not exist."},"list_id":{"type":"string","format":"uuid"}}}}}},"responses":{"201":{"description":"Import report","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CsvImportResult"}}}},"400":{"description":"Structured error with an actionable reason","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Structured error with an actionable reason","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/contacts/lists":{"get":{"tags":["Contacts"],"summary":"Every contact list with its member count","responses":{"200":{"description":"Lists","content":{"application/json":{"schema":{"type":"object","properties":{"lists":{"type":"array","items":{"$ref":"#/components/schemas/ContactList"}}}}}}}}},"post":{"tags":["Contacts"],"summary":"Create a contact list","description":"Names are unique per workspace, case-insensitively.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["name"],"properties":{"name":{"type":"string"},"description":{"type":"string"}}}}}},"responses":{"201":{"description":"Created","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ContactList"}}}},"400":{"description":"Structured error with an actionable reason","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"Structured error with an actionable reason","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/contacts/lists/{id}":{"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"get":{"tags":["Contacts"],"summary":"A list and a page of its members","description":"Page with offset until next_offset is null.","parameters":[{"name":"limit","in":"query","schema":{"type":"integer","maximum":500,"default":100}},{"name":"offset","in":"query","schema":{"type":"integer","default":0}}],"responses":{"200":{"description":"The list","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ContactListPage"}}}},"404":{"description":"Structured error with an actionable reason","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"patch":{"tags":["Contacts"],"summary":"Rename a list or change its description","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string"},"description":{"type":"string","nullable":true}}}}}},"responses":{"200":{"description":"Updated","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ContactList"}}}},"404":{"description":"Structured error with an actionable reason","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"Structured error with an actionable reason","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"delete":{"tags":["Contacts"],"summary":"Delete a list; the contacts on it are kept","responses":{"200":{"description":"Deleted","content":{"application/json":{"schema":{"type":"object"}}}},"404":{"description":"Structured error with an actionable reason","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/contacts/lists/{id}/members":{"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"post":{"tags":["Contacts"],"summary":"Add contacts to a list","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["contact_ids"],"properties":{"contact_ids":{"type":"array","items":{"type":"string","format":"uuid"},"maxItems":1000}}}}}},"responses":{"200":{"description":"{ added }","content":{"application/json":{"schema":{"type":"object"}}}},"404":{"description":"Structured error with an actionable reason","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"delete":{"tags":["Contacts"],"summary":"Remove contacts from a list","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["contact_ids"],"properties":{"contact_ids":{"type":"array","items":{"type":"string","format":"uuid"}}}}}}},"responses":{"200":{"description":"{ removed }","content":{"application/json":{"schema":{"type":"object"}}}},"404":{"description":"Structured error with an actionable reason","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/notes":{"get":{"tags":["Notes"],"summary":"List or search notes you can read","parameters":[{"name":"q","in":"query","schema":{"type":"string"}},{"name":"limit","in":"query","schema":{"type":"integer","maximum":100,"default":25}}],"responses":{"200":{"description":"Notes","content":{"application/json":{"schema":{"type":"object"}}}}}},"post":{"tags":["Notes"],"summary":"Create a note","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["body"],"properties":{"title":{"type":["string","null"]},"body":{"type":"string","maxLength":100000}}}}}},"responses":{"201":{"description":"Created","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Note"}}}},"400":{"description":"Structured error with an actionable reason","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/knowledge":{"get":{"tags":["Knowledge"],"summary":"The knowledge summary, or search it","description":"Without q: the brief, every source, and a table of contents. With q: ranked passages.","parameters":[{"name":"q","in":"query","schema":{"type":"string"}},{"name":"limit","in":"query","schema":{"type":"integer","maximum":25,"default":8}}],"responses":{"200":{"description":"Summary, or { hits }","content":{"application/json":{"schema":{"type":"object"}}}}}}},"/v1/knowledge/pages/{id}":{"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"get":{"tags":["Knowledge"],"summary":"Read an indexed page in full","responses":{"200":{"description":"The page","content":{"application/json":{"schema":{"type":"object"}}}},"404":{"description":"Structured error with an actionable reason","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/knowledge/sources":{"get":{"tags":["Knowledge"],"summary":"List knowledge sources","responses":{"200":{"description":"{ sources }","content":{"application/json":{"schema":{"type":"object"}}}}}},"post":{"tags":["Knowledge"],"summary":"Add a site to crawl, a page to fetch, or text to store","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["kind"],"properties":{"kind":{"type":"string","enum":["site","page","text"]},"url":{"type":"string","description":"For site and page."},"body":{"type":"string","maxLength":200000,"description":"For text."},"title":{"type":["string","null"]}}}}}},"responses":{"201":{"description":"Created","content":{"application/json":{"schema":{"$ref":"#/components/schemas/KnowledgeSource"}}}},"400":{"description":"Structured error with an actionable reason","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"402":{"description":"Structured error with an actionable reason","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/knowledge/sources/{id}":{"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"get":{"tags":["Knowledge"],"summary":"Read a source","responses":{"200":{"description":"The source","content":{"application/json":{"schema":{"$ref":"#/components/schemas/KnowledgeSource"}}}},"404":{"description":"Structured error with an actionable reason","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"post":{"tags":["Knowledge"],"summary":"Fetch a site or page again","responses":{"200":{"description":"{ id, status }","content":{"application/json":{"schema":{"type":"object"}}}},"404":{"description":"Structured error with an actionable reason","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"delete":{"tags":["Knowledge"],"summary":"Remove a source and its pages","responses":{"200":{"description":"{ deleted: true }","content":{"application/json":{"schema":{"type":"object"}}}},"404":{"description":"Structured error with an actionable reason","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/notes/{id}":{"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"get":{"tags":["Notes"],"summary":"Read a note","responses":{"200":{"description":"The note","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Note"}}}},"404":{"description":"Structured error with an actionable reason","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"patch":{"tags":["Notes"],"summary":"Update a note","description":"Merge patch: omit a field to keep it, send title null to clear it.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"title":{"type":["string","null"]},"body":{"type":"string"}}}}}},"responses":{"200":{"description":"Updated","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Note"}}}},"404":{"description":"Structured error with an actionable reason","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"delete":{"tags":["Notes"],"summary":"Delete a note","responses":{"200":{"description":"Deleted","content":{"application/json":{"schema":{"type":"object"}}}},"404":{"description":"Structured error with an actionable reason","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/secrets":{"get":{"tags":["Secrets"],"summary":"List secrets granted to this agent","description":"Names, types, and tags. No values, so finding the right secret is cheaper than opening one.","responses":{"200":{"description":"Secret metadata","content":{"application/json":{"schema":{"type":"object"}}}},"404":{"description":"Structured error with an actionable reason","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/secrets/{id}":{"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"get":{"tags":["Secrets"],"summary":"Open one secret","description":"Returns the credential itself, decrypted for an agent that was granted it. Values are encrypted at rest under a key held outside the database, and every read is written to the workspace activity log. An ungranted secret answers 404, the same as one that does not exist.","responses":{"200":{"description":"The secret","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Secret"}}}},"404":{"description":"Structured error with an actionable reason","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/mailboxes":{"get":{"tags":["Mailboxes"],"summary":"List the inboxes in this workspace","description":"Every agent in the workspace, plus how many inboxes the plan includes and how many are in use. Disposable inboxes are not listed: they belong to the public tool, not the account.","responses":{"200":{"description":"Mailboxes","content":{"application/json":{"schema":{"type":"object"}}}}}},"post":{"tags":["Mailboxes"],"summary":"Create an inbox","description":"One handle in, a live address out. Without domain_id the address is on the workspace subdomain. Past the included inboxes, free is refused with 402 and paid plans bill $1 a month for each extra.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["handle"],"properties":{"handle":{"type":"string","description":"3 to 63 chars, lowercase, globally unique"},"domain_id":{"type":"string","format":"uuid"},"display_name":{"type":"string"},"description":{"type":"string"}}}}}},"responses":{"201":{"description":"Created","content":{"application/json":{"schema":{"type":"object"}}}},"402":{"description":"Structured error with an actionable reason","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Structured error with an actionable reason","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"Structured error with an actionable reason","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/domains":{"get":{"tags":["Domains"],"summary":"List sending domains","description":"Includes the exact DNS records a pending domain still needs, so an agent can finish setup without opening the dashboard.","responses":{"200":{"description":"Domains","content":{"application/json":{"schema":{"type":"object"}}}}}},"post":{"tags":["Domains"],"summary":"Add a custom domain","description":"Returns the MX, DKIM, SPF, DMARC, and ownership records to publish. The three DKIM records are issued for the domain when it is added, so the response is the complete set; dkim_records_pending is true in the rare case it could not be registered yet, and a later read or verify call carries them.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["domain"],"properties":{"domain":{"type":"string","example":"agents.acme.com"}}}}}},"responses":{"201":{"description":"Created, with records to publish","content":{"application/json":{"schema":{"type":"object"}}}},"400":{"description":"Structured error with an actionable reason","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"402":{"description":"Structured error with an actionable reason","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"Structured error with an actionable reason","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"delete":{"tags":["Domains"],"summary":"Remove a custom domain","description":"The workspace subdomain cannot be removed: every agent lives on it.","parameters":[{"name":"id","in":"query","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Deleted","content":{"application/json":{"schema":{"type":"object"}}}},"404":{"description":"Structured error with an actionable reason","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/domains/{id}/verify":{"post":{"tags":["Domains"],"summary":"Check a domain now","description":"Reports which records resolve and whether the domain can send. Three things have to agree: the published records, the domain being accepted for sending, and DKIM signing. dkim_records_pending means the DKIM records themselves are not issued yet; dkim_status pending means they are published and confirmation is in progress, which needs nothing further from the caller. Pending domains are also checked nightly, so this is the fast path rather than the only one.","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Current state","content":{"application/json":{"schema":{"type":"object"}}}},"404":{"description":"Structured error with an actionable reason","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/webhooks":{"get":{"tags":["Webhooks"],"summary":"List webhook endpoints","description":"Secrets are not returned here; they are shown once, at creation.","responses":{"200":{"description":"Endpoints","content":{"application/json":{"schema":{"type":"object"}}}}}},"post":{"tags":["Webhooks"],"summary":"Add a webhook endpoint","description":"The response carries the signing secret once and never again. Deliveries are signed x-mcpmailer-signature: t=<unix>,v1=<hmac-sha256 of \"<t>.<body>\">, and carry x-mcpmailer-delivery-id, which is stable across retries so a receiver can ignore a repeat. The same id is sent as x-request-id, the name generic receivers already dedup on.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["url","events"],"properties":{"url":{"type":"string","format":"uri","description":"https only, and reachable from the public internet"},"events":{"type":"array","items":{"type":"string","enum":["message.received","message.sent","message.delivered","message.bounced","message.complained","message.filtered","approval.approved","approval.rejected","approval.expired","domain.verified","workspace.throttled","workspace.paused","workspace.resumed"]}},"mailbox_id":{"type":"string","format":"uuid","description":"Deliver only events for this agent. Omit for the whole workspace."},"headers":{"type":"object","additionalProperties":{"type":"string"},"description":"Extra request headers, for a receiver that requires its own auth. Values are write-only."},"template":{"type":"object","description":"Body shape override for a receiver that expects its own JSON, with {{event}} and {{data.subject}} placeholders. Omit for the standard envelope."}}}}}},"responses":{"201":{"description":"Created, with the signing secret","content":{"application/json":{"schema":{"type":"object"}}}},"400":{"description":"Structured error with an actionable reason","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"402":{"description":"Structured error with an actionable reason","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"patch":{"tags":["Webhooks"],"summary":"Update a webhook endpoint","description":"Changes an endpoint in place, keeping its id and its secret. Fields left out keep their current value. Setting active back to true also clears the failure count that turned it off.","parameters":[{"name":"id","in":"query","required":true,"schema":{"type":"string","format":"uuid"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"url":{"type":"string","format":"uri"},"events":{"type":"array","items":{"type":"string"}},"active":{"type":"boolean"},"mailbox_id":{"type":"string","format":"uuid","nullable":true,"description":"Null widens the endpoint back to the whole workspace."},"headers":{"type":"object","additionalProperties":{"type":"string"}},"template":{"type":"object"},"rotate_secret":{"type":"boolean","description":"Replace the signing secret. The new one is returned once, alongside previous_secret_valid_until. The old secret keeps verifying until then and deliveries are signed with both, so the receiver can be updated without a gap. A verifier must accept any v1 in the signature header for this to work."}}}}}},"responses":{"200":{"description":"Updated","content":{"application/json":{"schema":{"type":"object"}}}},"400":{"description":"Structured error with an actionable reason","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Structured error with an actionable reason","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"delete":{"tags":["Webhooks"],"summary":"Remove a webhook endpoint","parameters":[{"name":"id","in":"query","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Deleted","content":{"application/json":{"schema":{"type":"object"}}}},"404":{"description":"Structured error with an actionable reason","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/webhooks/deliveries":{"get":{"tags":["Webhooks"],"summary":"List delivery attempts","description":"What each attempt got back: the status code, the response body, and how long it took. Newest first, kept for thirty days. A status code means the receiver answered and rejected the request; an error with no status means it never arrived, which is DNS, TLS, or a timeout. delivery_id is shared by every attempt at the same event, so a run of rows with one id is one event being retried.","parameters":[{"name":"webhook_id","in":"query","schema":{"type":"string","format":"uuid"},"description":"Omit for every endpoint in the workspace."},{"name":"limit","in":"query","schema":{"type":"integer","default":25,"maximum":100}},{"name":"before","in":"query","schema":{"type":"string","format":"date-time"},"description":"Only attempts older than this. Page with the last row's `at`."}],"responses":{"200":{"description":"Attempts","content":{"application/json":{"schema":{"type":"object"}}}},"404":{"description":"Structured error with an actionable reason","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"post":{"tags":["Webhooks"],"summary":"Test or replay a delivery","description":"Sends something now and answers with exactly what came back. Nothing is queued and nothing is retried, so the response is the whole result. webhook_id sends a sample message.received event, and one that lands also clears the failure count that would disable the endpoint. delivery_id sends a stored payload again, byte for byte and under its original id, so a receiver that deduplicates recognises the repeat.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"webhook_id":{"type":"string","format":"uuid","description":"Send a test event."},"delivery_id":{"type":"string","format":"uuid","description":"Replay this stored attempt. Takes precedence over webhook_id."}}}}}},"responses":{"200":{"description":"What the endpoint answered","content":{"application/json":{"schema":{"type":"object"}}}},"400":{"description":"Structured error with an actionable reason","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Structured error with an actionable reason","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/signup":{"post":{"tags":["Signup"],"summary":"Provision a workspace, identity, and key","description":"No human account needed. The response includes a claim_url a person uses later to take ownership. Rate limited per IP; everything created starts free, probation tier, subdomain only, and cannot send until a person claims it and verifies their email.","security":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["handle"],"properties":{"handle":{"type":"string","pattern":"^[a-z0-9]([a-z0-9]|-){1,61}[a-z0-9]$"},"email":{"type":"string","format":"email","description":"Who should be able to claim it"},"workspace_name":{"type":"string"},"description":{"type":"string"}}}}}},"responses":{"201":{"description":"Provisioned","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Signup"}}}},"409":{"description":"Structured error with an actionable reason","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Structured error with an actionable reason","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}}},"components":{"securitySchemes":{"apiKey":{"type":"http","scheme":"bearer","description":"An mmk_ API key, scoped to one agent identity."}},"schemas":{"Error":{"type":"object","properties":{"error":{"type":"string"},"hint":{"type":"string"}},"required":["error"]},"SendResult":{"type":"object","properties":{"status":{"type":"string","enum":["sent","held","scheduled","rejected"]},"messageId":{"type":"string","format":"uuid"},"pendingId":{"type":"string","format":"uuid","description":"Set when status is held: the message is queued for a person to release. Ask about it at /v1/messages/pending/{id}. Not an error, and not to be retried."},"reason":{"type":"string","examples":["daily_send_quota_exhausted","recipient_suppressed"]},"retryAfter":{"type":"string","format":"date-time"},"scheduled_id":{"type":"string","format":"uuid","description":"Set when status is scheduled. The mixed case is the wire's own: the send path has always answered in camelCase and the scheduling path in snake, and renaming either here would describe an API nobody receives."},"send_at":{"type":"string","format":"date-time"}},"required":["status"]},"ScheduledSend":{"type":"object","properties":{"scheduled_id":{"type":"string","format":"uuid"},"subject":{"type":"string"},"to":{"type":"array","items":{"type":"string","format":"email"}},"send_at":{"type":"string","format":"date-time"},"booked_at":{"type":"string","format":"date-time"}}},"ScheduledOutcome":{"type":"object","properties":{"scheduled_id":{"type":"string","format":"uuid"},"status":{"type":"string","enum":["pending","sent","held","failed","cancelled"]},"subject":{"type":"string"},"to":{"type":"array","items":{"type":"string","format":"email"}},"send_at":{"type":"string","format":"date-time"},"message_id":{"type":"string","format":"uuid","nullable":true},"pending_id":{"type":"string","format":"uuid","nullable":true},"failure_reason":{"type":"string","nullable":true}}},"PendingSend":{"type":"object","properties":{"pending_id":{"type":"string","format":"uuid"},"subject":{"type":"string"},"to":{"type":"array","items":{"type":"string","format":"email"}},"snippet":{"type":"string"},"send_class":{"type":"string","enum":["reply","cold"]},"created_at":{"type":"string","format":"date-time"}}},"PendingOutcome":{"type":"object","properties":{"pending_id":{"type":"string","format":"uuid"},"status":{"type":"string","enum":["pending","approved","rejected","expired"]},"subject":{"type":"string"},"to":{"type":"array","items":{"type":"string","format":"email"}},"message_id":{"type":"string","format":"uuid","nullable":true,"description":"Set once it has actually gone out."},"decision_note":{"type":"string","nullable":true,"description":"Why a reviewer refused it, in their words."},"failure_reason":{"type":"string","nullable":true,"description":"Approved, but the send was still refused: suppression, quota, spend cap."},"decided_at":{"type":"string","format":"date-time","nullable":true}},"required":["pending_id","status"]},"Attachment":{"type":"object","properties":{"filename":{"type":"string"},"contentType":{"type":"string"},"size":{"type":"integer"}}},"Message":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"thread_id":{"type":["string","null"],"format":"uuid"},"direction":{"type":"string","enum":["in","out"]},"from":{"type":"string"},"to":{"type":"array","items":{"type":"string"}},"subject":{"type":"string"},"snippet":{"type":"string"},"unread":{"type":"boolean"},"status":{"type":"string"},"spam_verdict":{"type":["string","null"]},"attachments":{"type":"array","items":{"$ref":"#/components/schemas/Attachment"}},"created_at":{"type":"string","format":"date-time"}}},"Thread":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"subject":{"type":"string"},"last_message_at":{"type":"string","format":"date-time"},"messages":{"type":"array","items":{"$ref":"#/components/schemas/Message"}}}},"MailRule":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"match":{"type":"string","enum":["exact_email","domain"]},"value":{"type":"string"},"action":{"type":"string","enum":["allow","block"]}}},"Identity":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"handle":{"type":"string"},"display_name":{"type":["string","null"]},"description":{"type":["string","null"]},"filter_mode":{"type":"string","enum":["blacklist","whitelist"]},"mailbox_id":{"type":"string","format":"uuid"},"rules":{"type":"array","items":{"$ref":"#/components/schemas/MailRule"}}}},"Channel":{"type":"object","properties":{"kind":{"type":"string","enum":["email","phone"]},"value":{"type":"string"},"label":{"type":["string","null"]}}},"ContactInput":{"type":"object","description":"At least one of given_name, family_name, preferred_name, company_name.","properties":{"given_name":{"type":["string","null"]},"family_name":{"type":["string","null"]},"preferred_name":{"type":["string","null"]},"company_name":{"type":["string","null"]},"job_title":{"type":["string","null"]},"notes":{"type":["string","null"]},"domains":{"type":"array","items":{"type":"string"}},"channels":{"type":"array","items":{"$ref":"#/components/schemas/Channel"}}}},"VCardImport":{"type":"object","required":["vcard"],"properties":{"vcard":{"type":"string","description":"One or more BEGIN:VCARD blocks"}}},"ContactList":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"name":{"type":"string"},"description":{"type":"string","nullable":true},"member_count":{"type":"integer"},"created_at":{"type":"string","format":"date-time"},"updated_at":{"type":"string","format":"date-time"}}},"ContactListPage":{"allOf":[{"$ref":"#/components/schemas/ContactList"},{"type":"object","properties":{"offset":{"type":"integer"},"next_offset":{"type":"integer","nullable":true},"members":{"type":"array","items":{"$ref":"#/components/schemas/Contact"}}}}]},"CsvImportResult":{"type":"object","properties":{"created":{"type":"integer"},"matched_existing":{"type":"integer"},"added_to_list":{"type":"integer"},"list_id":{"type":"string","format":"uuid","nullable":true},"columns_understood":{"type":"array","items":{"type":"string"}},"skipped":{"type":"array","items":{"type":"object","properties":{"row":{"type":"integer"},"reason":{"type":"string"}}}},"failed":{"type":"array","items":{"type":"object"}},"contact_ids":{"type":"array","items":{"type":"string","format":"uuid"}}}},"Contact":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"name":{"type":"string"},"given_name":{"type":["string","null"]},"family_name":{"type":["string","null"]},"company_name":{"type":["string","null"]},"job_title":{"type":["string","null"]},"channels":{"type":"array","items":{"$ref":"#/components/schemas/Channel"}},"memories":{"type":"array","items":{"type":"object"}},"updated_at":{"type":"string","format":"date-time"}}},"Note":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"title":{"type":["string","null"]},"body":{"type":"string"},"created_at":{"type":"string","format":"date-time"},"updated_at":{"type":"string","format":"date-time"}}},"KnowledgeSource":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"kind":{"type":"string","enum":["site","page","text"]},"url":{"type":["string","null"]},"title":{"type":["string","null"]},"status":{"type":"string","enum":["pending","indexing","ready","failed"]},"error":{"type":["string","null"]},"pages":{"type":"integer"},"last_indexed_at":{"type":["string","null"],"format":"date-time"},"created_at":{"type":"string","format":"date-time"}}},"Secret":{"type":"object","description":"One vault secret, opened. Stored encrypted at rest under a key held outside the database; the value below exists only in this response.","properties":{"id":{"type":"string","format":"uuid"},"name":{"type":"string"},"type":{"type":"string","enum":["login","api_key","key_pair","ssh_key","other"]},"tags":{"type":"array","items":{"type":"string"}},"has_totp":{"type":"boolean"},"updated_at":{"type":"string","format":"date-time"},"secret":{"type":"object","description":"The credential itself. Its shape follows `type`: a login carries username, password, and an optional TOTP seed; an api_key carries key and an optional endpoint."}}},"Signup":{"type":"object","properties":{"handle":{"type":"string"},"address":{"type":"string","format":"email"},"api_key":{"type":"string"},"mcp_url":{"type":"string","format":"uri"},"claim_url":{"type":"string","format":"uri"}}}}}}