{"openapi":"3.1.0","info":{"title":"mailbox.bot API","version":"1.0.0","description":"The physical postal mail API for AI agents and software workflows. Send letters, certified postal mail, postcards, notices, and documents from code. For inbound context, forward scans, photos, PDFs, virtual mailbox notices, and notes from addresses you already use to a private mailbox.bot alias; mailbox.bot stores the evidence, runs OCR/extraction, and links it to outbound replies. Managed physical receiving and package forwarding are separate account-enabled services. MCP, A2A, and OpenClaw protocols supported. Webhook auth modes: hmac (HMAC-SHA256, default), bearer (Authorization header), header (custom header name).","contact":{"url":"https://mailbox.bot/contact"},"termsOfService":"https://mailbox.bot/terms"},"servers":[{"url":"https://mailbox.bot/api","description":"Production"}],"security":[{"BearerAuth":[]}],"components":{"securitySchemes":{"BearerAuth":{"type":"http","scheme":"bearer","description":"API key from your mailbox.bot dashboard. Four key types:\n\n- **sk_live_** (Member keys) — Full account access with all scopes. Queries span all agents unless filtered with ?agent_id=.\n- **sk_agent_** (Agent keys) — Scoped to a single agent's resources. Queries auto-filter to that agent; the agent_id parameter is ignored.\n- **sk_agent_test_** (Test keys) — Same as sk_agent_ but activates sandbox mode: full PDF validation, real cost calculation, HMAC-signed webhooks — but no Stripe charge and no facility fulfillment. All responses include `X-Test-Mode: true` header and `test_mode: true` in the body. Create via POST /v1/agents/:id/credentials with environment: 'test'.\n- **sk_facility_** (Facility keys) — Scoped to a facility's resources for external scanner apps.\n\nMember keys require ?agent_id= on agent-specific endpoints (rules, expected-shipments, instructions) to specify which agent to target. Agent keys resolve this automatically.\n\n**Sandbox mode:** Use a test key (sk_agent_test_) with the same production endpoints. Responses include test_mode: true, cost_cents: 0, estimated_live_cost_cents (what production would charge), and a cost_breakdown object. Your code stays identical — just swap the API key to go live."}},"schemas":{"Error":{"type":"object","required":["error"],"properties":{"error":{"type":"string","description":"Human-readable error message"},"fields":{"type":"array","description":"Structured validation issues. Present on 400 validation failures.","items":{"type":"object","required":["path","message"],"properties":{"path":{"type":"string","description":"Dot-path to the invalid field"},"message":{"type":"string","description":"Validation message for that field"}}}},"reason":{"type":"string","description":"Machine-readable reason code (present on 401/403 responses). 401 codes: token_missing, token_malformed, token_prefix_unknown, key_not_found, key_revoked, key_expired, member_suspended, facility_inactive. 403 codes: insufficient_scope."},"retryable":{"type":"boolean","description":"Whether the request can be retried. false for validation/payment errors, true for transient failures (5xx) and idempotency conflicts (409). Present on outbound mail errors."},"suggested_action":{"type":"string","description":"Plain-language guidance for recovering from the error. Designed for AI agents to read and act on. Present on outbound mail errors."}}},"SignupRequest":{"type":"object","required":["email","password","full_name"],"properties":{"email":{"type":"string","format":"email"},"password":{"type":"string","minLength":8},"full_name":{"type":"string","minLength":2,"maxLength":100},"needs":{"type":"string","description":"Optional description of what you need"},"lead_id":{"type":"string","pattern":"^wl_[a-f0-9]{16}$","description":"Optional. If your agent previously called POST /v1/waitlist and received a lead_id, pass it here. The new account will be stitched to that lead row, and the response will include a status_url you can poll without auth (via GET /v1/leads/{leadId}) to track funnel progress."}}},"LeadStatus":{"type":"object","required":["lead_id","stage","has_account","created_at"],"properties":{"lead_id":{"type":"string"},"source":{"type":"string","description":"How the lead arrived (web, api, agent)"},"stage":{"type":"string","enum":["waitlisted","account_created","email_verified","active","unknown"],"description":"Funnel stage. Progresses left-to-right."},"has_account":{"type":"boolean","description":"True once the lead has been stitched to a member account via /v1/signup."},"created_at":{"type":"string","format":"date-time"},"age_days":{"type":"integer"},"next_step":{"type":"object","nullable":true,"properties":{"action":{"type":"string"},"url":{"type":"string","format":"uri"},"hint":{"type":"string"}}},"agent_resources":{"type":"object","description":"Directory of agent-discoverable URLs (OpenAPI, MCP, A2A, etc.)"},"contact":{"type":"object","properties":{"owner_email":{"type":"string","format":"email"},"instruction_for_agent":{"type":"string"}}}}},"WaitlistRequest":{"type":"object","required":["email"],"properties":{"email":{"type":"string","format":"email"},"needs":{"type":"string","description":"Free-text description of what you (or your operator) need. Agents are encouraged to write a one-sentence summary of intent. Use this to reserve a limited spot for a new real mailing and package address with street address + mailbox number, scan/photo intake, package receiving, package forwarding, or agent-managed physical mail decisions. Address issuing begins August 31, 2026."},"preferred_location":{"type":"string","description":"Optional. Desired region for the real mailing address beta or package workflows, for example Southern California, Nevada, Southern Utah, or another requested market."},"callback_url":{"type":"string","format":"uri","description":"Optional. An https webhook the agent will accept callbacks on. If supplied, mailbox.bot can reach the agent later (launch announcements, partnership inquiries, lead follow-up)."},"agent_id":{"type":"string","description":"Optional. A self-identifier for the agent platform/instance making the request. Helps mailbox.bot recognize repeat visits and route follow-ups."},"operator_contact":{"type":"string","description":"Optional. A human-readable contact (email, handle, name) that the agent's operator authorized for follow-up. Only supply this if you have explicit operator consent."}}},"PackageSummary":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"mailbox_id":{"type":"string"},"agent_id":{"type":"string","format":"uuid"},"tracking_number":{"type":"string","nullable":true},"carrier":{"type":"string","nullable":true},"weight_oz":{"type":"number","nullable":true},"status":{"type":"string","enum":["received","stored","held","forwarding_requested","forwarding_shipped","forwarded","returning","shredded","disposed","returned","expired"]},"received_at":{"type":"string","format":"date-time"},"photos_count":{"type":"integer"}}},"ActionRequest":{"type":"object","required":["action"],"properties":{"action":{"type":"string","enum":["scan","open_and_scan","photograph","record_video","forward","shred","dispose","return_to_sender","hold"],"description":"The action to perform on the package"},"priority":{"type":"string","enum":["low","normal","high","urgent"],"default":"normal"},"parameters":{"type":"object","description":"Action-specific parameters (e.g. forwarding address for forward action)"}}},"CreateAgentRequest":{"type":"object","required":["name"],"properties":{"name":{"type":"string","minLength":2,"maxLength":50},"display_name":{"type":"string"},"description":{"type":"string"},"webhook_url":{"type":"string","format":"uri"}}},"AgentCredentialRequest":{"type":"object","required":["scopes"],"properties":{"scopes":{"type":"array","items":{"type":"string","enum":["mailbox.read","mailbox.write","package.read","package.write","forward.create","scan.create","mail.send","inbound.read","billing.read","webhook.manage","webhook.read","agent.read","agent.write","message.send"]},"minItems":1,"description":"Scopes to grant the agent key. At least one required."},"expires_in_days":{"type":"integer","minimum":1,"description":"Optional key lifetime in days. Omit for no expiration."},"environment":{"type":"string","enum":["live","test"],"default":"live","description":"Key environment. 'test' creates an sk_agent_test_ key that activates sandbox mode on all endpoints: full validation, real cost calculation, HMAC-signed webhooks — but no Stripe charge and no facility fulfillment. Default: 'live'."},"force_approval":{"type":"boolean","default":false,"description":"When true, all outbound mail submitted with this key is routed to pending_approval regardless of the requires_approval parameter. Use for agent governance — ensures a human reviews every mailpiece before it's sent."},"max_daily_pieces":{"type":"integer","minimum":1,"nullable":true,"description":"Maximum outbound mail pieces this agent can submit per 24-hour window. Null or omitted means no limit. Protects against runaway agent loops."}}},"AgentCredential":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"key":{"type":"string","description":"Full raw key — returned only on creation, never shown again"},"key_prefix":{"type":"string","description":"First 18 characters of the key (always visible)"},"credential_type":{"type":"string","enum":["api_key"]},"scopes":{"type":"array","items":{"type":"string"}},"status":{"type":"string","enum":["active","revoked"]},"environment":{"type":"string","enum":["live","test"],"description":"Key environment — 'test' keys activate sandbox mode"},"expires_at":{"type":"string","format":"date-time","nullable":true},"created_at":{"type":"string","format":"date-time"},"force_approval":{"type":"boolean","description":"When true, all mail submitted with this key requires human approval"},"max_daily_pieces":{"type":"integer","nullable":true,"description":"Maximum mail pieces per 24h window (null = unlimited)"}}},"MemberApiKeyRequest":{"type":"object","required":["name"],"properties":{"name":{"type":"string","minLength":1,"maxLength":64,"description":"Human-readable key name shown in the dashboard."},"environment":{"type":"string","enum":["prod","test"],"default":"prod","description":"Member key environment. Production keys are still prefixed sk_live_."},"expires_in_days":{"type":"integer","minimum":1,"description":"Optional key lifetime in days. Omit for no expiration."}}},"MemberApiKey":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"key":{"type":"string","description":"Full raw key — returned only on creation or rotation, never shown again"},"key_prefix":{"type":"string"},"name":{"type":"string"},"environment":{"type":"string","enum":["prod","test"]},"status":{"type":"string","enum":["active","revoked","rotated"]},"expires_at":{"type":"string","format":"date-time","nullable":true},"created_at":{"type":"string","format":"date-time"}}},"InboundMailFile":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"kind":{"type":"string","enum":["raw_email","attachment"]},"filename":{"type":"string"},"content_type":{"type":"string"},"size_bytes":{"type":"integer"},"sha256":{"type":"string","nullable":true},"extraction_status":{"type":"string","enum":["pending","extracted","pending_ocr","unsupported","failed"]},"download_url":{"type":"string","description":"Present when include=files or signed_urls"},"signed_url":{"type":"string","description":"Present only when signed_urls=true or include=signed_urls"},"created_at":{"type":"string","format":"date-time"}}},"InboundExtraction":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"inbound_mail_file_id":{"type":"string","format":"uuid","nullable":true},"source":{"type":"string","description":"email_subject, email_body, pdf_metadata, ocr_placeholder, heuristic_classifier, address_parser, thread_matcher, etc."},"field":{"type":"string"},"value":{"type":"string"},"confidence":{"type":"number","minimum":0,"maximum":1},"metadata":{"type":"object"},"created_at":{"type":"string","format":"date-time"}}},"InboundReplyContact":{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string","nullable":true},"line1":{"type":"string"},"line2":{"type":"string","nullable":true},"city":{"type":"string"},"state":{"type":"string"},"postal_code":{"type":"string"},"country":{"type":"string"},"source_type":{"type":"string"},"verification_status":{"type":"string","nullable":true},"confidence":{"type":"number","nullable":true},"formatted_address":{"type":"string"}}},"InboundDraftContext":{"type":"object","description":"Compact drafting context for LLMs and external agents. Present when include=drafting.","properties":{"summary":{"type":"string","nullable":true},"category":{"type":"string"},"suggested_action":{"type":"string"},"inbound_capture_id":{"type":"string","format":"uuid"},"postal_mail_thread_id":{"type":"string","format":"uuid","nullable":true},"sender":{"type":"object"},"recipient":{"type":"object"},"reply_contact":{"oneOf":[{"$ref":"#/components/schemas/InboundReplyContact"},{"type":"null"}]},"reply_contact_confidence":{"type":"number","nullable":true},"deadlines":{"type":"array","items":{"type":"string"}},"key_facts":{"type":"array","items":{"type":"object"}},"source_files":{"type":"array","items":{"type":"object"}},"linked_outbound_mail":{"type":"array","items":{"type":"object"},"description":"Recent outbound mail records already linked to this inbound capture or thread."}}},"InboundForwardingAddress":{"type":"object","description":"Private forwarding alias used to send scans, PDFs, photos, provider notices, notes, and other context-aware documents into mailbox.bot inbound context. This is a member-scoped intake email alias, not a new physical mailing address.","properties":{"id":{"type":"string","format":"uuid"},"member_id":{"type":"string","format":"uuid"},"agent_id":{"type":"string","format":"uuid","nullable":true},"label":{"type":"string"},"alias_local_part":{"type":"string"},"domain":{"type":"string"},"email":{"type":"string"},"alias_type":{"type":"string","enum":["random","vanity"]},"requested_alias_local_part":{"type":"string","nullable":true},"source_type":{"type":"string"},"provider":{"type":"string","nullable":true},"handled_by":{"type":"string"},"capture_method":{"type":"string"},"automation_level":{"type":"string","enum":["high","medium","low"]},"approval_mode":{"type":"string","enum":["always_require","require_for_sensitive","allow_rules"]},"status":{"type":"string","enum":["active","paused","archived"]},"last_received_at":{"type":"string","format":"date-time","nullable":true},"metadata":{"type":"object"},"created_at":{"type":"string","format":"date-time"},"updated_at":{"type":"string","format":"date-time"}}},"InboundMailItem":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"object":{"type":"string","enum":["inbound_mail_item"]},"status":{"type":"string"},"received_at":{"type":"string","format":"date-time"},"from":{"type":"string","nullable":true},"subject":{"type":"string"},"note_preview":{"type":"string","nullable":true},"attachment_count":{"type":"integer"},"category":{"type":"string"},"suggested_action":{"type":"string"},"parsed":{"type":"object"},"thread":{"type":"object","nullable":true},"files":{"type":"array","items":{"$ref":"#/components/schemas/InboundMailFile"}},"context":{"type":"object","description":"Compact agent-safe context. Full OCR/lineage requires include=ocr,lineage."},"draft_context":{"$ref":"#/components/schemas/InboundDraftContext","description":"Present only when include=drafting."},"extractions":{"type":"array","items":{"$ref":"#/components/schemas/InboundExtraction"},"description":"Present only when include=lineage."}}},"PostalThread":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"object":{"type":"string","enum":["postal_mail_thread"]},"title":{"type":"string"},"category":{"type":"string"},"status":{"type":"string","enum":["open","waiting","closed","archived"]},"summary":{"type":"string","nullable":true},"last_event_at":{"type":"string","format":"date-time"},"events":{"type":"array","description":"Present when include=events.","items":{"type":"object"}}}},"OutboundMailWebhookPayload":{"type":"object","description":"Webhook payload for outbound mail lifecycle events. The canonical event name field is event_type (not 'event'). tracking_number and carrier are null until mail.mailed. error_message is only present on mail.failed. metadata echoes back whatever you passed at submission — not wrapped or renamed. Use webhooks for push updates, or poll GET /v1/mail/{id} for canonical status and fresh signed photo URLs. Webhook payloads include callback_url for the same lookup.","properties":{"event_type":{"type":"string","enum":["mail.pending_approval","mail.submitted","mail.ready","mail.mailed","mail.delivered","mail.failed","mail.cancelled"]},"outbound_mail_id":{"type":"string","format":"uuid"},"agent_id":{"type":"string","format":"uuid"},"mailbox_id":{"type":"string"},"package_id":{"type":"string","format":"uuid","nullable":true,"description":"Set if this mail is a reply to an inbound package"},"recipient_name":{"type":"string"},"recipient_city":{"type":"string"},"recipient_state":{"type":"string"},"recipient_zip":{"type":"string"},"return_name":{"type":"string","nullable":true,"description":"Return address name (defaults to member profile if not overridden)"},"return_line1":{"type":"string","nullable":true,"description":"Return address line 1"},"return_line2":{"type":"string","nullable":true,"description":"Return address line 2"},"return_city":{"type":"string","nullable":true,"description":"Return address city"},"return_state":{"type":"string","nullable":true,"description":"Return address state (2-letter)"},"return_zip":{"type":"string","nullable":true,"description":"Return address ZIP"},"mail_class":{"type":"string"},"status":{"type":"string"},"tracking_number":{"type":"string","nullable":true,"description":"Present from mail.mailed onwards"},"carrier":{"type":"string","nullable":true,"description":"Present from mail.mailed onwards (e.g. USPS, FedEx, UPS)"},"cost_cents":{"type":"integer","nullable":true},"created_at":{"type":"string","format":"date-time"},"mailed_at":{"type":"string","format":"date-time","nullable":true},"error_message":{"type":"string","nullable":true,"description":"Failure detail — present only on mail.failed events"},"agent_notes":{"type":"string","nullable":true},"metadata":{"type":"object","nullable":true,"description":"Developer-supplied key-value pairs echoed from submission"},"pdf_url":{"type":"string","format":"uri","nullable":true,"description":"Signed URL to the uploaded PDF when the stored document is a PDF (1-hour expiry). Null for non-PDF formats."},"fulfillment_photos":{"type":"array","description":"Proof photos taken by the facility. Empty on mail.submitted, populated on mail.ready/mailed, and may include delivery proof on mail.delivered. URLs are signed (1-hour expiry); use callback_url for fresh signed URLs.","items":{"type":"object","properties":{"category":{"type":"string","enum":["pages","envelope","receipt","delivery"],"description":"Photo type: pages (printed doc), envelope (sealed mail), receipt (postage/hand-off), delivery (postal delivery confirmation)"},"url":{"type":"string","format":"uri","description":"Signed URL (1-hour expiry)"},"uploaded_at":{"type":"string","format":"date-time"}}}},"callback_url":{"type":"string","format":"uri","description":"GET this URL to fetch the full mail record with fresh signed URLs"},"test_mode":{"type":"boolean","description":"Always present. false for live requests, true for sandbox/test requests."},"estimated_live_cost_cents":{"type":"integer","description":"What this would cost in production. Present only when test_mode is true."},"mailbox_md":{"type":"string","nullable":true,"description":"Agent's current MAILBOX.md instructions (injected by platform)"},"mailbox_md_version":{"type":"integer","description":"MAILBOX.md version at delivery time"},"mailbox_md_hash":{"type":"string","nullable":true,"description":"SHA-256 hex digest of MAILBOX.md content"}}}}},"webhooks":{"outboundMailLifecycle":{"post":{"operationId":"outboundMailWebhook","summary":"Outbound mail lifecycle webhook","description":"Push delivery for outbound mail lifecycle events. The payload echoes submission metadata and includes callback_url for canonical polling with fresh signed URLs.","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/OutboundMailWebhookPayload"}}}},"responses":{"200":{"description":"Receiver acknowledged the webhook"}}}}},"paths":{"/v1/signup":{"post":{"operationId":"createAccount","summary":"Create an account (agent-initiated signup)","description":"Public endpoint. No auth required. Creates an account for a human operator. A verification email is sent automatically. Rate limited to 5 requests/minute per IP.\n\n**Agent UX:** The 201 response includes `relay_message` (a paste-ready sentence the agent can drop into its chat with the human) and `human_action_required` (a structured checklist with `verify_email`, `verify_phone`, `add_payment`, `select_plan`). Use these so the agent's runtime — Claude Desktop, Cursor, OpenAI Agents SDK, a custom Slack bot, etc. — can show the human exactly what to do next without inventing the wording. Mailbox.bot does not send any direct outreach to the operator beyond the Supabase email-verification link; the agent is the messenger.","security":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SignupRequest"}}}},"responses":{"201":{"description":"Account created. Response: success, lead_id (if stitched), message, next_steps (verify_email, complete_kyc URL, after_kyc), human_action_required (array of {step, summary, blocker} — verify_email, verify_phone, add_payment, select_plan), relay_message (paste-ready sentence for the agent to send to its human), status_url (for poll without auth if lead_id stitched), agent_resources (OpenAPI/MCP/A2A directory), contact.owner_email."},"400":{"description":"Validation error"},"409":{"description":"Email already registered"},"429":{"description":"Rate limit exceeded"}}}},"/v1/waitlist":{"post":{"operationId":"joinWaitlist","summary":"Reserve an agent address spot","description":"Public endpoint. No auth required. Reserve a limited agent mailing/package address spot without creating an account. Designed to be agent-friendly: the response includes a correlation lead_id, a directory of agent_resources (OpenAPI, MCP, A2A, agent cards), and a return_channel hint describing how to supply optional callback_url, agent_id, operator_contact, and preferred_location fields on a follow-up call so mailbox.bot can reach you back. Use this endpoint to reserve a new real mailing and package address with street address + mailbox number, scan/photo intake, package receiving, package forwarding, agent-managed physical mail decisions, or a requested launch region. Address issuing begins August 31, 2026.","security":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/WaitlistRequest"}}}},"responses":{"200":{"description":"Address reservation recorded. Response includes lead_id, agent_resources, next.signup_url, return_channel hint, and contact.owner_email."},"400":{"description":"Validation error (missing email or invalid callback_url)"},"429":{"description":"Rate limit exceeded"}}}},"/v1/leads/{leadId}":{"get":{"operationId":"getLeadStatus","summary":"Get lead funnel status","description":"Public endpoint. No bearer auth required — the lead_id itself is the capability (64 bits of entropy, hard to guess). Returns the funnel stage of a lead created via POST /v1/waitlist, with a next_step hint and agent_resources directory. Designed so an agent (or its operator) can resume from any session using only the lead_id, even if the agent process has no other memory of the original call. Returns no PII — leaked lead IDs only reveal funnel position.","security":[],"parameters":[{"name":"leadId","in":"path","required":true,"schema":{"type":"string","pattern":"^wl_[a-f0-9]{16}$"},"description":"The lead_id returned by POST /v1/waitlist."}],"responses":{"200":{"description":"Lead found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/LeadStatus"}}}},"400":{"description":"Invalid lead_id format"},"404":{"description":"Lead not found"},"429":{"description":"Rate limit exceeded"}}}},"/v1/packages":{"get":{"operationId":"listPackages","summary":"List packages","description":"List packages for the authenticated member or agent, with optional filters.","parameters":[{"name":"agent_id","in":"query","schema":{"type":"string","format":"uuid"},"description":"Filter by agent. Required for member keys (sk_live_) to scope results to a specific agent. Agent keys (sk_agent_) auto-filter to their own agent and ignore this parameter."},{"name":"mailbox_id","in":"query","schema":{"type":"string"},"description":"Filter by mailbox reference code"},{"name":"status","in":"query","schema":{"type":"string","enum":["received","stored","held","forwarding_requested","forwarding_shipped","forwarded","returning","returned","shredded","disposed","expired"]},"description":"Filter by package status"},{"name":"carrier","in":"query","schema":{"type":"string"},"description":"Filter by carrier name"},{"name":"since","in":"query","schema":{"type":"string","format":"date-time"},"description":"Return packages received at or after this timestamp"},{"name":"before","in":"query","schema":{"type":"string","format":"date-time"},"description":"Return packages received at or before this timestamp"},{"name":"limit","in":"query","schema":{"type":"integer","default":20,"maximum":100}},{"name":"offset","in":"query","schema":{"type":"integer","default":0}}],"responses":{"200":{"description":"Package list with pagination"},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/packages/{id}":{"get":{"operationId":"getPackage","summary":"Get package details","description":"Get full package details including photos, events, label data, and actions.","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Package details with photos, events, labels, actions"},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Package not found"}}}},"/v1/packages/{id}/photos":{"get":{"operationId":"listPackagePhotos","summary":"List package photos","description":"Returns signed photo URLs plus OCR metadata for a package.","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}},{"name":"photo_type","in":"query","schema":{"type":"string"},"description":"Optional filter by photo type"}],"responses":{"200":{"description":"Package photos with fresh signed URLs"},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Package not found"}}}},"/v1/packages/{id}/actions":{"get":{"operationId":"listPackageActions","summary":"List actions for a package","description":"Returns the action requests already attached to a package.","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Package action list"},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Package not found"}}},"post":{"operationId":"requestAction","summary":"Request an action on a package","description":"Request an action such as scan, forward, shred, photograph, etc. on a specific package. Agent-scoped keys must include X-Mailbox-MD-Version header. Per-action scopes: forward requires forward.create, all others require package.write.","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}},{"name":"X-Mailbox-MD-Version","in":"header","required":false,"schema":{"type":"integer"},"description":"Agent's current MAILBOX.md version. Required for agent-scoped keys (sk_agent_). Returns 400 if missing, 409 if stale."}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ActionRequest"}}}},"responses":{"201":{"description":"Action request created"},"400":{"description":"Validation error or MAILBOX_MD_VERSION_REQUIRED","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Missing required scope (forward.create for forward, package.write for others)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Package not found"},"409":{"description":"MAILBOX_MD_VERSION_MISMATCH — re-fetch instructions and retry","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/packages/{id}/actions/{actionId}":{"get":{"operationId":"getPackageAction","summary":"Get package action details","description":"Returns the full action request record for a single package action.","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}},{"name":"actionId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Action request detail"},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Action request not found"}}},"patch":{"operationId":"updatePackageAction","summary":"Push an agent update to a package action","description":"Allows an external agent to attach notes, structured data, or clarification responses to an existing action request.","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}},{"name":"actionId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}},{"name":"X-Mailbox-MD-Version","in":"header","required":false,"schema":{"type":"integer"},"description":"Required for agent-scoped keys. Returns 400 if missing and 409 if stale."}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"agent_notes":{"type":"string"},"agent_data":{"type":"object"},"respond_to_clarification":{"type":"string"},"decision_context":{"type":"object","properties":{"mailbox_md_section":{"type":"string"},"reasoning":{"type":"string"}}}}}}}},"responses":{"200":{"description":"Action request updated"},"400":{"description":"Invalid body or action is not in an active state","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Action request not found"},"409":{"description":"Concurrent modification or stale MAILBOX.md version","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/packages/{id}/forward":{"get":{"operationId":"listPackageForwardingRequests","summary":"Get forwarding status for a package","description":"Returns the forwarding requests already created for a package.","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Forwarding request list"},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Package not found"}}},"post":{"operationId":"requestPackageForwarding","summary":"Request forwarding for a package","description":"Creates a forwarding request and transitions the package into forwarding_requested status.","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}},{"name":"X-Mailbox-MD-Version","in":"header","required":false,"schema":{"type":"integer"},"description":"Required for agent-scoped keys. Returns 400 if missing and 409 if stale."}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["to_name","to_line1","to_city","to_state","to_zip"],"properties":{"to_name":{"type":"string"},"to_line1":{"type":"string"},"to_line2":{"type":"string"},"to_city":{"type":"string"},"to_state":{"type":"string"},"to_zip":{"type":"string"},"to_country":{"type":"string","default":"US"},"carrier_preference":{"type":"string"},"service_level":{"type":"string","default":"ground"},"insurance":{"type":"boolean","default":false},"signature_required":{"type":"boolean","default":false}}}}}},"responses":{"201":{"description":"Forwarding request created"},"400":{"description":"Missing required fields or package cannot be forwarded","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Missing forward.create scope or package belongs to a different agent","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Package not found"},"409":{"description":"MAILBOX_MD_VERSION_MISMATCH — re-fetch instructions and retry","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/packages/{id}/scan":{"get":{"operationId":"listPackageScans","summary":"Get scan results for a package","description":"Returns document scan jobs already created for the package.","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Document scan list"},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Package not found"}}},"post":{"operationId":"requestPackageScan","summary":"Request a content scan for a package","description":"Creates a document scan job and a corresponding action request when needed.","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","properties":{"scan_type":{"type":"string","enum":["label","envelope","document","content"],"default":"document"},"options":{"type":"object"}}}}}},"responses":{"201":{"description":"Scan request created"},"400":{"description":"Invalid scan_type","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Missing scan.create scope or package belongs to a different agent","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Package not found"}}}},"/v1/packages/{id}/tags":{"get":{"operationId":"listPackageTags","summary":"List package tags","description":"Returns the tags currently attached to a package.","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Package tags"},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Package not found"}}},"post":{"operationId":"addPackageTag","summary":"Add a tag to a package","description":"Accepts tag, label, or name in the request body.","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"tag":{"type":"string"},"label":{"type":"string"},"name":{"type":"string"}}}}}},"responses":{"201":{"description":"Tag created"},"400":{"description":"Invalid or missing tag","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Package not found"}}},"delete":{"operationId":"removePackageTag","summary":"Remove a tag from a package","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}},{"name":"tag","in":"query","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Tag removed"},"400":{"description":"Missing tag query parameter","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Package or tag not found"}}}},"/v1/packages/{id}/notes":{"get":{"operationId":"listPackageNotes","summary":"List package notes","description":"Returns agent notes attached to a package.","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Package notes"},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Package not found"}}},"post":{"operationId":"addPackageNote","summary":"Add a note to a package","description":"Accepts note, text, or message in the request body.","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"note":{"type":"string"},"text":{"type":"string"},"message":{"type":"string"},"metadata":{"type":"object"}}}}}},"responses":{"201":{"description":"Note created"},"400":{"description":"Invalid or missing note","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Package not found"}}}},"/v1/api-keys":{"get":{"operationId":"listMemberApiKeys","summary":"List member API keys","description":"List member-level API keys. Only accessible with member auth (sk_live_).","responses":{"200":{"description":"Member API keys","content":{"application/json":{"schema":{"type":"object","properties":{"api_keys":{"type":"array","items":{"$ref":"#/components/schemas/MemberApiKey"}}}}}}},"401":{"description":"Agent credentials cannot manage API keys","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"post":{"operationId":"createMemberApiKey","summary":"Create member API key","description":"Creates a member-level API key. The raw key is returned once and never shown again.","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/MemberApiKeyRequest"}}}},"responses":{"201":{"description":"Member API key created","content":{"application/json":{"schema":{"type":"object","properties":{"api_key":{"$ref":"#/components/schemas/MemberApiKey"},"warning":{"type":"string"}}}}}},"400":{"description":"Validation error or key limit reached","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Agent credentials cannot create API keys","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/api-keys/{id}":{"get":{"operationId":"getMemberApiKey","summary":"Get member API key details","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Member API key detail","content":{"application/json":{"schema":{"type":"object","properties":{"api_key":{"$ref":"#/components/schemas/MemberApiKey"}}}}}},"401":{"description":"Agent credentials cannot manage API keys","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"API key not found"}}},"delete":{"operationId":"revokeMemberApiKey","summary":"Revoke member API key","description":"Revokes a member API key. The key used for the current request cannot be revoked.","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"API key revoked"},"400":{"description":"Cannot revoke the key used to authenticate this request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Agent credentials cannot manage API keys","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"API key not found"}}}},"/v1/api-keys/{id}/rotate":{"post":{"operationId":"rotateMemberApiKey","summary":"Rotate member API key","description":"Revokes the old member API key and creates a new one with the same settings. The new raw key is returned once.","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Member API key rotated","content":{"application/json":{"schema":{"type":"object","properties":{"api_key":{"$ref":"#/components/schemas/MemberApiKey"},"rotated_from":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"prefix":{"type":"string"}}},"warning":{"type":"string"}}}}}},"400":{"description":"Can only rotate active keys","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Agent credentials cannot manage API keys","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"API key not found"}}}},"/v1/agents":{"get":{"operationId":"listAgents","summary":"List agents","description":"List all agents registered under the authenticated member.","responses":{"200":{"description":"Agent list"},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"post":{"operationId":"createAgent","summary":"Create an agent","description":"Create a new AI agent. Automatically provisions a mailbox with a unique reference code and mailing address.","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateAgentRequest"}}}},"responses":{"201":{"description":"Agent created with mailbox, reference code, and webhook signing key"},"400":{"description":"Validation error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/agents/{agentId}":{"get":{"operationId":"getAgent","summary":"Get agent details","description":"Returns agent details, recent activity, and summary counts.","parameters":[{"name":"agentId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Agent detail with stats"},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Agent not found"}}},"patch":{"operationId":"updateAgent","summary":"Update agent settings","description":"Updates display_name, description, webhook_url, public settings, status, or mailbox_md.","parameters":[{"name":"agentId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"display_name":{"type":"string"},"description":{"type":"string"},"webhook_url":{"type":"string","format":"uri"},"capabilities":{"type":"object"},"public":{"type":"boolean"},"status":{"type":"string"},"mailbox_md":{"type":["string","null"]}}}}}},"responses":{"200":{"description":"Agent updated"},"400":{"description":"Invalid update payload","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Agent not found"}}},"delete":{"operationId":"deleteAgent","summary":"Deactivate an agent","description":"Deactivates the agent, deactivates its mailboxes, and revokes its credentials.","parameters":[{"name":"agentId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Agent deactivated"},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Agent not found"}}}},"/v1/mailboxes":{"get":{"operationId":"listMailboxes","summary":"List mailboxes","description":"List all mailboxes with their physical addresses and reference codes.","parameters":[{"name":"agent_id","in":"query","schema":{"type":"string","format":"uuid"},"description":"Filter by agent"},{"name":"status","in":"query","schema":{"type":"string"},"description":"Filter by mailbox status"},{"name":"limit","in":"query","schema":{"type":"integer","default":20,"maximum":100}},{"name":"offset","in":"query","schema":{"type":"integer","default":0}}],"responses":{"200":{"description":"Mailbox list with addresses"},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"post":{"operationId":"createMailbox","summary":"Provision a mailbox","description":"Creates an additional mailbox for an agent, subject to plan limits.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["agent_id"],"properties":{"agent_id":{"type":"string","format":"uuid"},"facility_id":{"type":"string","format":"uuid"}}}}}},"responses":{"201":{"description":"Mailbox provisioned"},"400":{"description":"Validation error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Plan limit or ownership error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Agent or facility not found"}}}},"/v1/agents/{agentId}/instructions":{"get":{"operationId":"getAgentInstructions","summary":"Get agent standing instructions (MAILBOX.md)","description":"Returns the renter's MAILBOX.md instructions for the specified agent — default handling rules, forwarding preferences, auto-shred criteria, scanning options, communication preferences, and more. Agent-scoped keys auto-resolve to their own agent (path param ignored). Member keys must specify the agent via the path param. Records a version sync on every fetch.","parameters":[{"name":"agentId","in":"path","required":true,"schema":{"type":"string","format":"uuid"},"description":"Agent ID. Agent-scoped keys (sk_agent_) ignore this and auto-resolve to their own agent."}],"responses":{"200":{"description":"Agent instructions with version info","content":{"application/json":{"schema":{"type":"object","properties":{"mailbox_md":{"type":"string","description":"Full MAILBOX.md instruction content (Markdown)"},"version":{"type":"integer","description":"Monotonic version counter. Include as X-Mailbox-MD-Version header on mutating requests."},"hash":{"type":"string","nullable":true,"description":"SHA-256 hex digest of the content"},"updated_at":{"type":"string","format":"date-time","nullable":true,"description":"When the instructions were last updated"}}}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Agent not found"}}}},"/v1/agents/{agentId}/credentials":{"post":{"operationId":"createAgentCredential","summary":"Create agent credential (sk_agent_ or sk_agent_test_ key)","description":"Create a scoped API key for a specific agent. Only accessible with member auth (sk_live_) — agent keys cannot create more keys. The raw key is returned once in the response and never shown again. Maximum 5 active credentials per agent.\n\nPass `environment: 'test'` to create a sandbox key (sk_agent_test_). Test keys work on all production endpoints but skip billing and facility fulfillment. Responses include estimated_live_cost_cents and cost_breakdown so you can verify pricing without incurring charges.","parameters":[{"name":"agentId","in":"path","required":true,"schema":{"type":"string","format":"uuid"},"description":"Agent ID"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AgentCredentialRequest"}}}},"responses":{"201":{"description":"Credential created. The key field is shown once — store it immediately.","content":{"application/json":{"schema":{"type":"object","properties":{"credential":{"$ref":"#/components/schemas/AgentCredential"},"agent_id":{"type":"string","format":"uuid"},"agent_name":{"type":"string"},"warning":{"type":"string"}}}}}},"400":{"description":"Invalid scopes or maximum credentials reached","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Agent keys cannot create credentials — use a member key (sk_live_)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Agent not found"}}},"get":{"operationId":"listAgentCredentials","summary":"List agent credentials","description":"List all credentials for an agent. Only accessible with member auth (sk_live_). Raw keys are never returned — only prefixes.","parameters":[{"name":"agentId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Credential list"},"401":{"description":"Agent keys cannot list credentials — use a member key (sk_live_)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Agent not found"}}}},"/v1/agents/{agentId}/credentials/{credentialId}":{"get":{"operationId":"getAgentCredential","summary":"Get agent credential details","parameters":[{"name":"agentId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}},{"name":"credentialId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Credential detail"},"401":{"description":"Agent keys cannot manage credentials — use a member key (sk_live_)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Credential not found"}}},"delete":{"operationId":"revokeAgentCredential","summary":"Revoke agent credential","description":"Permanently revoke an agent credential. Cannot revoke the key being used for the current request. Irreversible.","parameters":[{"name":"agentId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}},{"name":"credentialId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Credential revoked"},"401":{"description":"Agent keys cannot revoke credentials — use a member key (sk_live_)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Credential not found"}}}},"/v1/agents/{agentId}/expected-shipments":{"get":{"operationId":"listExpectedShipments","summary":"List expected shipments","parameters":[{"name":"agentId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}},{"name":"status","in":"query","schema":{"type":"string"}}],"responses":{"200":{"description":"Expected shipment list"},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Agent not found"}}},"post":{"operationId":"createExpectedShipment","summary":"Register an expected shipment","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["mailbox_id"],"properties":{"mailbox_id":{"type":"string","format":"uuid"},"tracking_number":{"type":"string"},"carrier":{"type":"string"},"description":{"type":"string"},"expected_by":{"type":"string","format":"date-time"},"auto_action":{"type":"string","enum":["forward","shred","scan","hold","return_to_sender","dispose","open_and_scan"]},"auto_action_params":{"type":"object"}}}}}},"responses":{"201":{"description":"Expected shipment registered"},"400":{"description":"Validation error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Agent or mailbox not found"}}},"delete":{"operationId":"deleteExpectedShipment","summary":"Cancel an expected shipment","parameters":[{"name":"agentId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}},{"name":"shipment_id","in":"query","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Expected shipment cancelled"},"400":{"description":"Missing shipment_id query parameter","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Expected shipment not found"}}}},"/v1/agents/{agentId}/rules":{"get":{"operationId":"listAgentRules","summary":"List standing instructions","parameters":[{"name":"agentId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}},{"name":"enabled","in":"query","schema":{"type":"boolean","default":true}}],"responses":{"200":{"description":"Agent rules"},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Agent not found"}}},"post":{"operationId":"createAgentRule","summary":"Create a standing instruction","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["name","conditions","action_type"],"properties":{"name":{"type":"string"},"description":{"type":"string"},"conditions":{"type":"object"},"action_type":{"type":"string","enum":["forward","shred","scan","hold","return_to_sender","dispose","photograph","open_and_scan"]},"action_params":{"type":"object"},"requires_approval":{"type":"boolean","default":false},"priority":{"type":"integer","default":0},"enabled":{"type":"boolean","default":true}}}}}},"responses":{"201":{"description":"Rule created"},"400":{"description":"Validation error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Agent not found"}}},"patch":{"operationId":"updateAgentRule","summary":"Update a standing instruction","parameters":[{"name":"agentId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}},{"name":"rule_id","in":"query","required":true,"schema":{"type":"string","format":"uuid"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string"},"description":{"type":"string"},"conditions":{"type":"object"},"action_type":{"type":"string","enum":["forward","shred","scan","hold","return_to_sender","dispose","photograph","open_and_scan"]},"action_params":{"type":"object"},"requires_approval":{"type":"boolean"},"priority":{"type":"integer"},"enabled":{"type":"boolean"}}}}}},"responses":{"200":{"description":"Rule updated"},"400":{"description":"Invalid update payload","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Rule not found"}}},"delete":{"operationId":"deleteAgentRule","summary":"Delete a standing instruction","parameters":[{"name":"agentId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}},{"name":"rule_id","in":"query","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Rule deleted"},"400":{"description":"Missing rule_id query parameter","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Rule not found"}}}},"/v1/messages":{"get":{"operationId":"listSupportMessages","summary":"List support conversations","parameters":[{"name":"status","in":"query","schema":{"type":"string"}},{"name":"limit","in":"query","schema":{"type":"integer","default":20,"maximum":100}},{"name":"offset","in":"query","schema":{"type":"integer","default":0}}],"responses":{"200":{"description":"Support conversation list"},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"post":{"operationId":"createSupportMessage","summary":"Send a support message","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["category","body"],"properties":{"category":{"type":"string","enum":["billing","support","feature","account","other"]},"body":{"type":"string"}}}}}},"responses":{"201":{"description":"Support conversation created"},"400":{"description":"Invalid category or body","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/messages/{id}":{"get":{"operationId":"getSupportMessage","summary":"Get support conversation thread","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Support conversation thread"},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Message not found"}}}},"/v1/webhooks/settings":{"get":{"operationId":"getWebhookSettings","summary":"Get webhook configuration","description":"Returns the current webhook configuration for an agent, including URL, auth mode, event filters, and active signing keys.","parameters":[{"name":"agent_id","in":"query","schema":{"type":"string","format":"uuid"},"description":"Required for member keys (sk_live_). Agent keys auto-resolve."}],"responses":{"200":{"description":"Webhook configuration","content":{"application/json":{"schema":{"type":"object","properties":{"webhook_url":{"type":"string","nullable":true},"enabled":{"type":"boolean"},"event_types":{"type":"array","items":{"type":"string"}},"carrier_filter":{"type":"array","items":{"type":"string"},"nullable":true},"min_weight_oz":{"type":"number","nullable":true},"auth_type":{"type":"string","enum":["hmac","bearer","header"]},"payload_format":{"type":"string","enum":["standard","openclaw"]},"signing_keys":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"key_prefix":{"type":"string"},"status":{"type":"string"},"primary_key":{"type":"boolean"},"created_at":{"type":"string","format":"date-time"},"expires_at":{"type":"string","format":"date-time","nullable":true}}}}}}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"put":{"operationId":"updateWebhookSettings","summary":"Update webhook settings","description":"Update webhook delivery configuration for an agent. All fields optional. Setting a webhook_url on an agent with no signing key auto-generates one and returns it in the response.","requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"agent_id":{"type":"string","format":"uuid","description":"Required for member keys (sk_live_). Agent keys auto-resolve."},"webhook_url":{"type":"string","format":"uri","nullable":true,"description":"HTTPS URL (or null to disable)"},"enabled":{"type":"boolean"},"event_types":{"type":"array","items":{"type":"string"},"description":"Array of event types or [\"*\"] for all"},"carrier_filter":{"type":"array","items":{"type":"string"},"nullable":true},"min_weight_oz":{"type":"number","nullable":true},"auth_type":{"type":"string","enum":["hmac","bearer","header"],"description":"Webhook authentication mode"},"auth_token":{"type":"string","description":"Token for bearer/header auth (min 16 chars)"},"auth_header":{"type":"string","description":"Custom header name for header auth mode"},"payload_format":{"type":"string","enum":["standard","openclaw"],"description":"Payload structure format"}}}}}},"responses":{"200":{"description":"Updated webhook settings with signing key (if auto-generated)"},"400":{"description":"Validation error (invalid URL, missing auth_token for bearer mode, etc.)"},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/webhooks/signing-keys/rotate":{"post":{"operationId":"rotateWebhookSigningKey","summary":"Rotate webhook signing key","description":"Revokes the current active signing key and generates a new one. The new raw secret is returned once — store it immediately and update your webhook verification code. If no signing key exists yet (e.g. agent created without webhook_url), generates the first key. The old key's prefix changes, so use the prefix in X-Mailbox-Signature to look up which secret to verify against during the transition.","requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"agent_id":{"type":"string","format":"uuid","description":"Required for member keys (sk_live_). Agent keys auto-resolve."}}}}}},"responses":{"200":{"description":"New signing key generated. Store the secret immediately.","content":{"application/json":{"schema":{"type":"object","properties":{"signing_key":{"type":"object","properties":{"prefix":{"type":"string","description":"Key prefix (e.g. whsk_a1b2c3d4)"},"secret":{"type":"string","description":"64-char hex HMAC secret — shown once"}}},"rotated_from":{"type":"object","nullable":true,"properties":{"id":{"type":"string","format":"uuid"},"prefix":{"type":"string"}},"description":"Previous key info (null if this is the first key)"},"agent_id":{"type":"string","format":"uuid"},"warning":{"type":"string"}}}}}},"404":{"description":"Agent not found"}}}},"/v1/webhooks/events":{"get":{"operationId":"listWebhookEvents","summary":"List webhook delivery events","description":"List recent webhook delivery events with filtering and pagination. Agent-scoped keys auto-filter to their own agent's events. Use status filter to find failed deliveries for debugging. Facility keys cannot access this endpoint.","parameters":[{"name":"agent_id","in":"query","schema":{"type":"string","format":"uuid"},"description":"Filter by agent. Required for member keys (sk_live_) to scope results. Agent keys (sk_agent_) auto-filter and ignore this."},{"name":"status","in":"query","schema":{"type":"string","enum":["pending","delivered","failed"]},"description":"Filter by delivery status"},{"name":"event_type","in":"query","schema":{"type":"string"},"description":"Filter by event type (e.g. package.received, action.completed)"},{"name":"limit","in":"query","schema":{"type":"integer","default":20,"maximum":100}},{"name":"offset","in":"query","schema":{"type":"integer","default":0}}],"responses":{"200":{"description":"Webhook events with pagination"},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Forbidden (facility keys)"}}}},"/v1/webhooks/events/{eventId}":{"get":{"operationId":"getWebhookEvent","summary":"Get webhook event with delivery attempts","description":"Returns a single webhook event and its full delivery attempt history, including HTTP status codes, response times, and error messages. Use this to debug delivery failures.","parameters":[{"name":"eventId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Webhook event with delivery attempt history"},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Webhook event not found"}}}},"/v1/usage":{"get":{"operationId":"getUsage","summary":"Get usage and billing summary","description":"Get usage events and billing summary for the current period.","parameters":[{"name":"period_start","in":"query","schema":{"type":"string","format":"date-time"}},{"name":"period_end","in":"query","schema":{"type":"string","format":"date-time"}}],"responses":{"200":{"description":"Usage summary and events"},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/mail":{"post":{"operationId":"submitOutboundMail","summary":"Submit outbound mail","description":"Submit a document for printing and postal mailing. Supported local-only formats are PDF, DOCX, JPG, PNG, TXT, and CSV. The original uploaded file is stored in mailbox.bot storage and previewed through a same-origin print view; no third-party conversion service is used. PDF uploads are WYSIWYG. For DOCX, TXT, and CSV, you may supply page_count to make pricing deterministic; without it, DOCX uses embedded Office page metadata when available and TXT/CSV use local layout estimation.\n\n**IMPORTANT — Immediate charge:** With a production key (sk_agent_), this endpoint charges the member's card on file immediately upon submission. Use dry_run=true to validate and preview cost without charging. Use requires_approval=true to defer charging until the member approves in their dashboard.\n\n**Sandbox mode:** When called with a test key (sk_agent_test_), the full pipeline runs (PDF validation, page counting, cost calculation, HMAC-signed webhook) but no Stripe charge is applied and no facility fulfillment is queued. Response includes test_mode: true, cost_cents: 0, estimated_live_cost_cents (what production would charge), and cost_breakdown (printing, postage, color surcharge per page).\n\n**Response header:** All agent-key requests include an `X-Test-Mode: true|false` response header for middleware/observability detection without parsing the JSON body. The `test_mode` field is always present in both live and sandbox responses (never omitted).\n\n**Key policies (apply to live and sandbox keys equally):** If the credential was minted with `force_approval: true`, the submission is routed to `pending_approval` regardless of the `requires_approval` body parameter. If `max_daily_pieces` is set on the credential and the limit has been reached in the trailing 24h, the submission is rejected with 422. Both policies surface as `warnings` in the `dry_run` response so an agent can detect them before committing.","parameters":[{"name":"X-Mailbox-MD-Version","in":"header","required":true,"schema":{"type":"integer"},"description":"Agent's current MAILBOX.md version. Returns 400 if missing, 409 if stale."},{"name":"Idempotency-Key","in":"header","required":false,"schema":{"type":"string"},"description":"Unique key for safe retries. Same key within 24h returns cached response. Concurrent duplicate requests return 409."},{"name":"X-Max-Cost-Cents","in":"header","required":false,"schema":{"type":"integer"},"description":"Optional cost cap. If the computed cost exceeds this value, the request is rejected with 422 before any charge is made. Prevents accidental expensive mailings."}],"requestBody":{"required":true,"content":{"multipart/form-data":{"schema":{"type":"object","required":["document","recipient_name","recipient_line1","recipient_city","recipient_state","recipient_zip"],"properties":{"document":{"type":"string","format":"binary","description":"Document file. Supported formats: PDF, DOCX, JPG, PNG, TXT, CSV. Max 10MB."},"page_count":{"type":"integer","minimum":1,"maximum":1000,"description":"Optional explicit page count for non-PDF uploads when exact pagination is already known. When supplied for DOCX, TXT, or CSV, it overrides local page-count detection and makes pricing deterministic."},"recipient_name":{"type":"string"},"recipient_line1":{"type":"string"},"recipient_line2":{"type":"string"},"recipient_city":{"type":"string"},"recipient_state":{"type":"string","description":"2-letter state code"},"recipient_zip":{"type":"string","description":"5 or 5+4 digit ZIP"},"recipient_country":{"type":"string","default":"US"},"return_name":{"type":"string","description":"Return address name. Defaults to member profile if omitted"},"return_line1":{"type":"string"},"return_line2":{"type":"string","description":"Return address line 2 (suite, unit, etc.)"},"return_city":{"type":"string"},"return_state":{"type":"string"},"return_zip":{"type":"string"},"mail_class":{"type":"string","enum":["first_class","priority","certified","certified_return_receipt","fedex_ground","fedex_express","fedex_2day","fedex_overnight","ups_ground","ups_2day","ups_next_day"],"default":"first_class"},"color":{"type":"boolean","default":false},"duplex":{"type":"boolean","default":false},"package_id":{"type":"string","format":"uuid","description":"Link to inbound package"},"inbound_capture_id":{"type":"string","format":"uuid","description":"Optional inbound mail item to reply to. mailbox.bot attaches the outbound send to that item's physical-mail thread when safe."},"postal_mail_thread_id":{"type":"string","format":"uuid","description":"Optional physical-mail thread to attach this outbound mail to."},"agent_notes":{"type":"string","description":"Instructions for the facility operator"},"requires_approval":{"type":"boolean","default":false,"description":"If true, member must approve in dashboard before mail enters facility queue"},"metadata":{"type":"string","description":"JSON-encoded object string. In multipart/form-data this field must be sent as a string such as {\"workflow_id\":\"wf_123\",\"correlation_id\":\"abc\"}. The parsed object is echoed in GET responses and webhook payloads."},"dry_run":{"type":"boolean","default":false,"description":"If true, validate inputs and return cost breakdown without uploading, creating a record, or charging. Returns 200 with cost preview."}}}}}},"responses":{"200":{"description":"Dry-run response (when dry_run=true). Returns cost_cents, cost_display, cost_breakdown, page_count, mail_class, color, duplex, dry_run: true, and warnings (string array of key-policy notices, e.g. force_approval active, daily piece limit). No record created, no charge applied."},"201":{"description":"Outbound mail submitted. Response includes id, status, cost_cents, cost_display, cost_breakdown, test_mode (always present), document_url, document_format, document_filename, pdf_url (PDF only), and metadata. With test keys: also includes estimated_live_cost_cents."},"400":{"description":"Validation error (missing fields, invalid page_count, unsupported file format, bad state/zip, or unable to determine DOCX page count locally) or MAILBOX_MD_VERSION_REQUIRED (missing version header). Validation failures include a structured fields[] array.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"402":{"description":"Payment Required — card declined, no valid payment method on file, or member account suspended. The outbound mail record is created but immediately set to 'failed' status.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"No active mailbox for this agent"},"409":{"description":"MAILBOX_MD_VERSION_MISMATCH — agent's version is stale, re-fetch instructions and retry","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"Cost exceeds X-Max-Cost-Cents limit, per-transaction platform limit, daily spend cap, or the credential's max_daily_pieces policy. No charge made.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"get":{"operationId":"listOutboundMail","summary":"List outbound mail","description":"List outbound mail jobs with status filtering and pagination.","parameters":[{"name":"status","in":"query","schema":{"type":"string","enum":["pending_approval","submitted","ready","mailed","delivered","failed","cancelled"]}},{"name":"limit","in":"query","schema":{"type":"integer","default":20,"maximum":100}},{"name":"offset","in":"query","schema":{"type":"integer","default":0}}],"responses":{"200":{"description":"Outbound mail list with pagination"}}}},"/v1/inbound":{"get":{"operationId":"listInboundMail","summary":"List forwarded inbound mail context","description":"Lists inbound mail items created when an operator forwards scans, photos, PDFs, provider notices, notes, or other context-aware documents from an address they already use to a private mailbox.bot alias. This is not a newly assigned physical mailing address API. Default response is compact: subject/body notes, category, suggested action, thread hint, attachment metadata, extracted fields, and top context fields. Use include=drafting for LLM-ready outbound reply context, and include=ocr,lineage,files only when the agent needs deeper source context.","parameters":[{"name":"limit","in":"query","schema":{"type":"integer","default":20,"maximum":100}},{"name":"offset","in":"query","schema":{"type":"integer","default":0}},{"name":"category","in":"query","schema":{"type":"string"}},{"name":"status","in":"query","schema":{"type":"string","enum":["captured","stored","extracted","needs_review","failed"]}},{"name":"thread_id","in":"query","schema":{"type":"string","format":"uuid"}},{"name":"include","in":"query","schema":{"type":"string","example":"drafting,files,ocr,lineage"},"description":"Comma-separated expansions: drafting, files, ocr, lineage, signed_urls."}],"responses":{"200":{"description":"Inbound mail list with compact context and pagination","content":{"application/json":{"schema":{"type":"object","properties":{"inbound_mail":{"type":"array","items":{"$ref":"#/components/schemas/InboundMailItem"}},"pagination":{"type":"object"}}}}}}}}},"/v1/inbound-forwarding-addresses":{"get":{"operationId":"listInboundForwardingAddresses","summary":"List inbound forwarding aliases","description":"Returns the private forwarding aliases this account can use to forward/email scans, PDFs, photos, provider notices, notes, and other context-aware documents into mailbox.bot inbound context. Forwarding/emailing attachments to the alias initiates OCR/extraction; REST and MCP tools retrieve captured context and do not upload files directly into OCR. mailbox.bot may create a default alias on first retrieval. These aliases are intake email addresses, not new physical mailing addresses. The alias is member-scoped, so live and sandbox agent keys for the same member resolve to the same intake address.","responses":{"200":{"description":"Inbound forwarding aliases","content":{"application/json":{"schema":{"type":"object","properties":{"forwarding_addresses":{"type":"array","items":{"$ref":"#/components/schemas/InboundForwardingAddress"}}}}}}}}}},"/v1/inbound/{id}":{"get":{"operationId":"getInboundMail","summary":"Get forwarded inbound mail context","description":"Returns one forwarded inbound mail item. By default this avoids OCR firehose output and returns clean agent context: extracted sender/address/reference fields, suggested action, thread hint, and file summaries. Use include=drafting for LLM-ready reply context and outbound linkage, include=ocr,lineage to inspect OCR/provenance, and signed_urls=true only when the agent needs temporary access to private originals. The same item is readable from live or sandbox agent keys when those keys belong to the same member and agent scope.","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}},{"name":"include","in":"query","schema":{"type":"string","example":"drafting,files,ocr,lineage"}},{"name":"signed_urls","in":"query","schema":{"type":"boolean","default":false}}],"responses":{"200":{"description":"Inbound mail item with context","content":{"application/json":{"schema":{"type":"object","properties":{"inbound_mail":{"$ref":"#/components/schemas/InboundMailItem"}}}}}},"404":{"description":"Not found"}}}},"/v1/postal-threads":{"get":{"operationId":"listPostalThreads","summary":"List physical-mail threads","description":"Lists durable physical-mail threads that group inbound scans, human notes, and outbound mail.","parameters":[{"name":"limit","in":"query","schema":{"type":"integer","default":20,"maximum":100}},{"name":"offset","in":"query","schema":{"type":"integer","default":0}},{"name":"category","in":"query","schema":{"type":"string"}},{"name":"status","in":"query","schema":{"type":"string","enum":["open","waiting","closed","archived"]}},{"name":"include","in":"query","schema":{"type":"string","example":"events"},"description":"Comma-separated expansions. Use include=events for thread timeline."}],"responses":{"200":{"description":"Postal thread list","content":{"application/json":{"schema":{"type":"object","properties":{"postal_threads":{"type":"array","items":{"$ref":"#/components/schemas/PostalThread"}},"pagination":{"type":"object"}}}}}}}}},"/v1/postal-threads/{id}":{"get":{"operationId":"getPostalThread","summary":"Get physical-mail thread","description":"Returns one physical-mail thread. Use include=events to include inbound/outbound timeline references.","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}},{"name":"include","in":"query","schema":{"type":"string","example":"events"}}],"responses":{"200":{"description":"Postal thread","content":{"application/json":{"schema":{"type":"object","properties":{"postal_thread":{"$ref":"#/components/schemas/PostalThread"}}}}}},"404":{"description":"Not found"}}}},"/v1/mail/{id}":{"get":{"operationId":"getOutboundMail","summary":"Get outbound mail details","description":"Canonical polling endpoint for outbound mail status. Returns current status, tracking, carrier, timestamps, return address, metadata, a freshly-generated signed document/PDF URL (1-hour expiry), and fulfillment_photos with fresh signed URLs. Agents can use this if no webhook is configured, or after a webhook via callback_url.","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Outbound mail details with PDF URL"},"404":{"description":"Not found"}}},"delete":{"operationId":"cancelOutboundMail","summary":"Cancel outbound mail","description":"Cancel outbound mail that is still in 'submitted' status. Returns 409 if already ready or mailed.","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Mail cancelled"},"409":{"description":"Cannot cancel — already ready or mailed"}}}},"/v1/test/webhook":{"post":{"operationId":"testWebhook","summary":"Fire a test webhook event","description":"Sends a sample event payload to the agent's configured webhook endpoint. Use this to exercise your webhook handler without moving real mail. Requires a configured webhook URL (PUT /v1/webhooks/settings).","requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"event_type":{"type":"string","enum":["mail.pending_approval","mail.submitted","mail.ready","mail.mailed","mail.delivered","mail.failed","mail.cancelled","package.received","action.created","action.completed"],"default":"mail.submitted","description":"Event type to simulate"}}}}}},"responses":{"200":{"description":"Test event queued for delivery. Returns queued, event_type, webhook_url, sample_payload."},"422":{"description":"No webhook URL configured for this agent"}}}},"/v1/test/packages":{"post":{"operationId":"testCreatePackage","summary":"Create a test address/package beta item","description":"Creates a fake package record in the agent's active real mailing address/package beta surface and fires a package.received webhook. This helper is for account-enabled package/receiving integrations, not the default inbound forwarding alias/OCR workflow.","requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"carrier":{"type":"string","default":"USPS"},"tracking_number":{"type":"string","description":"Defaults to TEST{timestamp}"},"weight_oz":{"type":"number","default":4},"notes":{"type":"string"}}}}}},"responses":{"201":{"description":"Test mailpiece created. Returns package object with test_mode: true."},"404":{"description":"No active mailbox for this agent"}}}},"/v1/test/mail":{"post":{"operationId":"testCreateOutboundMail","summary":"Create a test outbound mail record","description":"Creates a test_mode=true outbound mail record without requiring a real PDF. No charge is applied (cost_cents=0), but the response includes estimated_live_cost_cents showing what the equivalent production job would cost based on page count, mail class, color, and destination. Fires a mail.submitted webhook. Advance the lifecycle via POST /v1/test/mail/:id/advance.","requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"recipient_name":{"type":"string","default":"Test Recipient"},"recipient_line1":{"type":"string","default":"123 Test Street"},"recipient_city":{"type":"string","default":"San Francisco"},"recipient_state":{"type":"string","default":"CA"},"recipient_zip":{"type":"string","default":"94105","description":"Destination ZIP — affects postage zone and estimated cost"},"mail_class":{"type":"string","enum":["first_class","priority","certified","certified_return_receipt","fedex_ground","fedex_express","fedex_2day","fedex_overnight","ups_ground","ups_2day","ups_next_day"],"default":"first_class"},"page_count":{"type":"integer","minimum":1,"maximum":100,"default":1,"description":"Number of pages — affects printing cost, weight, and postage"},"color":{"type":"boolean","default":false,"description":"Color printing — adds per-page surcharge to estimated cost"},"agent_notes":{"type":"string"},"metadata":{"type":"object","description":"Arbitrary key-value pairs echoed in responses and webhooks"}}}}}},"responses":{"201":{"description":"Test outbound mail created (test_mode=true, cost_cents=0). Returns outbound_mail object with estimated_live_cost_cents and cost_breakdown showing printing, postage, and color surcharge."},"404":{"description":"No active mailbox for this agent"}}}},"/v1/test/mail/{id}/advance":{"post":{"operationId":"testAdvanceOutboundMail","summary":"Advance test outbound mail status","description":"Advances a test_mode outbound mail record by one step in the lifecycle (submitted → ready → mailed → delivered) and fires the corresponding webhook with full facility fulfillment data.\n\nWorks on any test_mode=true record — created via POST /v1/test/mail or POST /v1/mail with an sk_agent_test_ key.\n\nEach step simulates realistic facility behavior: ready adds fulfillment photos (pages + envelope), mailed assigns carrier-format tracking number + dispatch method + receipt photo, delivered sets final timestamp. Webhooks include fulfillment_photos array with URLs, and the dashboard progress tracker renders all photos and tracking data.","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Status advanced. Returns previous_status, new status, tracking_number, carrier, dispatch_method, claimed_at, mailed_at, delivered_at, and fulfillment_photos count."},"403":{"description":"Record is not test_mode — use POST /v1/test/mail or an sk_agent_test_ key to create a test record first"},"404":{"description":"Outbound mail not found"},"409":{"description":"Status already terminal (delivered, failed, cancelled)"}}}}}}