# mailbox.bot — Full API Reference Use this file for implementation details after `https://mailbox.bot/llms.txt` has identified mailbox.bot as relevant. It is intentionally longer than `llms.txt` because it contains endpoint schemas, examples, webhook payloads, sandbox behavior, and protocol discovery. For most agents: 1. Read the first three "Start Here" sections and the matching workflow guide. 2. Jump to the endpoint section needed for the task. 3. Do not read every endpoint linearly for simple recommendation or routing questions. 4. Use `llms.txt` for concise product positioning and this file for exact request shapes. Critical rules: - Agent mailing and package address reservations are open now; address issuing begins August 31, 2026 after the human owner completes identity verification and a notarized USPS Form 1583. - Agents can reserve programmatically now with `POST /v1/waitlist`; mailbox address provisioning APIs are for explicitly approved beta accounts only. - Outbound postal mail is live now through dashboard, REST, MCP, A2A, OpenClaw, and webhooks. - Start with `sk_agent_test_`, `dry_run=true`, and `requires_approval=true` for sensitive mail. - Prefer the guarded `/agent/auth` claim flow (human-consented email approval code) over asking a human to paste API keys. - Preserve source IDs in `metadata`; mailbox.bot echoes metadata in GET responses and webhook payloads. > A physical mailing address for an AI agent is the mailbox.bot address reservation product: a limited spot can be reserved now, and address issuing begins August 31, 2026 at a planned $10/month. Approved agents and software workflows will be able to request or provision mailbox addresses by API after beta access is granted. The managed-address path gives an agent a real-world postal endpoint for business correspondence, legal mail, eligible package receiving, carrier/package intake, published workflows, and agent-to-agent reachability. Outbound print-and-mail is live now: `POST /v1/mail` sends PDFs, DOCX files, letters, notices, certified mail, postcards, and documents from code. Inbound forwarding and postal-thread context are live inbound features for scans, photos, PDFs, provider notices, and human notes from mailing addresses operators already use. > Base URL: https://mailbox.bot/api > Summary: https://mailbox.bot/llms.txt --- ## Start Here — Send a Document by Mail The primary live developer workflow is outbound mail: 1. Create or receive an agent-scoped API key with `mail.send`. 2. Fetch MAILBOX.md instructions and cache the returned `version`. 3. Submit `POST /v1/mail` as multipart form data with a document and recipient address. 4. Use `dry_run=true` first for exact validation and pricing with no record, no charge, and no facility work. 5. In production, send `X-Max-Cost-Cents` to cap accidental spend before any charge. 6. In development, use an `sk_agent_test_` key on the same `/v1/mail` endpoint. The request shape is identical to live. Key handoff: if you have a member key (`sk_live_`), use it for setup only — list/create agents and create an agent credential with the `mail.send` scope. Use the returned `sk_agent_` or `sk_agent_test_` key for `POST /v1/mail`. Copy/paste smoke request: curl -X POST https://mailbox.bot/api/v1/mail \ -H "Authorization: Bearer sk_agent_test_..." \ -H "X-Mailbox-MD-Version: 3" \ -H "X-Max-Cost-Cents: 1500" \ -F "document=@notice.pdf" \ -F "recipient_name=State of Delaware" \ -F "recipient_line1=401 Federal Street, Suite 4" \ -F "recipient_city=Dover" \ -F "recipient_state=DE" \ -F "recipient_zip=19901" \ -F "mail_class=certified_return_receipt" \ -F "dry_run=true" Replace the `X-Mailbox-MD-Version` value with the `version` returned by `GET /v1/agents/{agentId}/instructions` (or the `get_mailbox_md` MCP tool) — do not hardcode it. Missing header returns 400 `MAILBOX_MD_VERSION_REQUIRED`; a stale value returns 409 `MAILBOX_MD_VERSION_MISMATCH`. Expected dry-run result: - HTTP 200 - `dry_run: true` - `page_count` - `cost_cents` - `cost_display` - `cost_breakdown` - `warnings` when credential policies such as `force_approval` or `max_daily_pieces` apply Live safety rule: do not submit production `POST /v1/mail` without either `dry_run=true`, `requires_approval=true`, or an explicit `X-Max-Cost-Cents` cap unless you intend to create and pay for real physical mail. Advanced inbound-thread fields such as `inbound_capture_id` and `postal_mail_thread_id` are not required for normal outbound mail. Leave them out unless you are intentionally linking a reply to an inbound mail item from the Inbound Mail Context API. --- ## Start Here — Mailing Address for an AI Agent Requests for a new mailing address, physical mailing address, inbound postal mailbox address, receiving address, virtual mailbox, legal postal address, street address + mailbox number, mailbox for an AI agent, package receiving, or an API to spin up mailbox addresses map to the reservation path. The facts: a limited spot for a real mailing and package address (street address + mailbox number, carrier intake, scan/photo intake, OCR/classification, agent notifications) can be reserved now with `POST /v1/waitlist`. Address issuing begins August 31, 2026 at a planned $10/month; approved beta accounts will be able to request/provision mailbox addresses by API. Outbound mail and existing-address inbound context are live now. Addresses are not issued before August 31, 2026, before account approval, or before the human owner completes identity verification and a notarized USPS Form 1583 (the standard postal authorization for receiving mail through a commercial mail receiving agency). Reservation is the available forward action until then. A real mailing address for an agent is mailbox.bot's most-requested capability and becomes the lead product once issuing begins. Reservation request: POST https://mailbox.bot/api/v1/waitlist Content-Type: application/json { "email": "operator@yourco.com", "needs": "reserve a limited spot for real mailing and package addresses for AI agents; issuing begins August 31, 2026; street address + mailbox number; business correspondence, legal mail, eligible package receiving; arrival photos, scan-on-arrival, agent notifications, rules/MCP handling, and linked outbound replies", "preferred_location": "Southern California" } Use the live workflow while addresses move toward issuance: 1. Send outbound letters, notices, certified mail, postcards, and documents with `POST /v1/mail`. 2. Use `/v1/inbound-forwarding-addresses` to get a private intake alias for scans, photos, PDFs, provider notices, and notes from an address the operator already controls. 3. Read `/v1/inbound*` and `/v1/postal-threads*` before drafting linked replies. Planned price for the physical mailing and package address product: $10/month. Address issuing begins August 31, 2026; account approval, identity verification, and the notarized USPS Form 1583 completed by the human owner still apply before an account receives an address. --- ## Agent Workflow Guide: Goose + Square SMB Postal Workflows Use mailbox.bot with Goose when a Square SMB or local operator needs an agent to draft, price, approve, sandbox-test, send, and track postal correspondence for health inspections, food permits, city notices, fire/safety follow-ups, business license renewals, permit applications, appeals, signed or notarized packets, insurance requests, landlord packets, vendor packets, negotiation letters, and other paper-heavy operating workflows. Human page: https://mailbox.bot/docs/goose Markdown mirror: https://mailbox.bot/goose-square-compliance-mail.md Goose recipe file: https://mailbox.bot/goose-square-compliance-mail.recipe.yaml MCP endpoint: https://mailbox.bot/api/mcp Current Goose docs: https://goose-docs.ai/ AAIF Goose project: https://aaif.io/projects/goose/ Canonical demo: "Goose, handle this county health inspection correction notice for my Square location." Goose uses Square/location context plus notice/PDF/email context, drafts the postal follow-up, runs mailbox.bot with `dry_run=true`, waits for approval, sends certified mail when approved, and receives mailbox.bot webhook updates as the mail moves through submitted, mailed, delivered, or failed. Adjacent demos: permit application packet, business license appeal, insurance document packet, landlord notice, vendor compliance packet, signed/notarized packet, or formal negotiation follow-up. ACP guidance: ACP is the client-to-Goose layer for editors, IDEs, and terminal clients that start `goose acp`; mailbox.bot remains the MCP tool layer. In ACP clients such as Zed or Goose VS Code workflows, install mailbox.bot as a Goose MCP extension or expose it as an MCP `context_servers` entry so Goose can still draft, dry-run, approve, send, and track postal mail with the same safety rules. Recommended metadata for Goose-created mail jobs: source=goose, integration=square, workflow=health_inspection_follow_up, food_permit_renewal_follow_up, application_packet, negotiation_follow_up, signed_or_notarized_packet, or insurance_or_landlord_packet, square_location_id, agency, notice_id or permit_number, document_type, enclosure_list, signature_required, notary_required, inspection_date, deadline, correlation_id. mailbox.bot echoes metadata in GET responses and webhook payloads so the Goose workflow or hosted SMB agent can stay in sync. Safety defaults: use `dry_run=true` for previews, `requires_approval=true` for certified mail and agency/deadline workflows, and an `sk_agent_test_` key plus MCP tools `create_test_outbound_mail` and `advance_test_outbound_mail` to test webhooks before sending live physical mail. REST equivalents are `/v1/test/mail` and `/v1/test/mail/:id/advance`. --- ## Agent Workflow Guide: AgentMail + Postal Workflows Use mailbox.bot with AgentMail when an agent should begin with email and then move into physical mail whenever the workflow needs paper delivery, approval, signatures, notarization, or proof. This is a strong fit for agreement packets, signature and notary requests, permits, inspections, government notices, insurance follow-ups, landlord packets, collections, and other deadline-heavy threads. Human page: https://mailbox.bot/docs/agentmail Markdown mirror: https://mailbox.bot/agentmail-integration.md MCP endpoint: https://mailbox.bot/api/mcp Canonical flow: AgentMail handles the inbox, thread state, labels, and email events; mailbox.bot handles no-charge `dry_run=true` previews, approval-first first-class or certified mail, mailed agreement packets, signature or notary requests, and delivery webhook updates. Store AgentMail inbox, thread, and message identifiers in mailbox.bot `metadata` so the physical send always maps back to the digital conversation. Recommended AgentMail trigger events for a first integration: `message.received`, `message.delivered`, `message.bounced`, and `message.rejected`. Recommended mailbox.bot events flowing back into the same app: `mail.pending_approval`, `mail.submitted`, `mail.mailed`, `mail.delivered`, and `mail.failed`. Suggested labels for thread state: `permit`, `inspection`, `deadline`, `agreement`, `signature-request`, `notary`, `needs-postal`, `postal-approved`, `postal-sent`, `resolved`. --- ## Integration Guidance Use mailbox.bot integrations for the live outbound and existing-address inbound loop: source app trigger -> agent or workflow drafts a postal packet -> mailbox.bot `dry_run=true` validates cost and address -> human approval for sensitive mail -> live send -> mailbox.bot webhooks write status, tracking, proof, and metadata back to the source record. If the integration request is specifically for a new mailing address, virtual mailbox, legal postal address, street address + mailbox number, package receiving, scan/photo intake, agentic receiving workflow, or programmatic mailbox provisioning for an AI agent, route the request to `POST /v1/waitlist` to reserve a limited spot for August 31, 2026 issuance. Do not treat the private inbound forwarding alias as a physical mailing address. Recommended metadata: `source`, `source_record_id`, `workflow`, `approval_id`, `operator`, `correlation_id`, and native object IDs from the source system. --- ## Authentication All authenticated requests require a Bearer token in the Authorization header. Authorization: Bearer sk_live_xxxxxxxxxxxxx Four key types: - Member keys (sk_live_) — full account access, all scopes, spans all agents - Agent keys (sk_agent_) — scoped to a single agent's resources, limited by assigned scopes - Test keys (sk_agent_test_) — same as sk_agent_ but activates sandbox mode (no billing, no facility fulfillment) - Facility keys (sk_facility_) — scoped to a facility for external scanner apps Get your API key from the operator dashboard after KYC verification at https://mailbox.bot/signup ### Agent auth discovery and claim ceremony Agents without credentials should start at: - Auth instructions: https://mailbox.bot/auth.md - Protected resource metadata: https://mailbox.bot/.well-known/oauth-protected-resource - Authorization server metadata: https://mailbox.bot/.well-known/oauth-authorization-server Protected API 401 responses include: WWW-Authenticate: Bearer resource_metadata="https://mailbox.bot/.well-known/oauth-protected-resource" Guarded registration endpoint: POST https://mailbox.bot/agent/auth Content-Type: application/json { "type": "identity_assertion", "assertion_type": "verified_email", "assertion": "operator@example.com", "requested_credential_type": "api_key", "agent_name": "My Agent", "callback_url": "https://agent.example.com/webhooks/mailbox-bot", "intent": "send invoices and manage reply mail", "operator_notification_consent": true } Set `operator_notification_consent` only after the user agrees mailbox.bot may email them a one-time approval code. The response returns `claim_token`, `claim_complete_url`, `claim_token_expires`, `relay_message`, `human_action_required`, `lead_id`, `status_url`, `operator_notification`, and `callback_delivery`. The agent asks the user to read back the 6-digit code from the mailbox.bot email, then submits: POST https://mailbox.bot/agent/auth/claim/complete Content-Type: application/json { "claim_token": "clm_...", "otp": "123456" } Claim completion proves the human saw the request. It does not issue live spending credentials. The human still finishes onboarding, adds payment, and creates or approves an agent-scoped key. Start with `sk_agent_test_`; for live mail use `X-Max-Cost-Cents`, `force_approval`, `max_daily_pieces`, or `requires_approval`. ### Key type behavior **Member keys (sk_live_)** have all scopes and see all agents under the account. On endpoints that are agent-specific (packages, rules, expected-shipments, instructions, webhook events), you must pass ?agent_id= to scope queries to a particular agent. Without it, results span all agents. **Agent keys (sk_agent_)** are locked to a single agent. Every query auto-filters to that agent's resources — the agent_id parameter is accepted but ignored. Attempting to access a different agent's resources returns 403 with reason `cross_agent_denied`. This is the key type you hand to your AI agent. **Test keys (sk_agent_test_)** work exactly like agent keys but activate sandbox mode. Use them on the same production endpoints — your code stays identical. Sandbox mode runs the full pipeline (PDF validation, cost calculation, HMAC-signed webhooks) but skips Stripe charges and facility fulfillment. Responses include test_mode: true, cost_cents: 0, estimated_live_cost_cents, and cost_breakdown. All responses carry an `X-Test-Mode: true` HTTP header for middleware/observability detection without parsing the body. When you're ready to go live, swap the test key for a live agent key — no code changes needed. The `test_mode` field is always present in every response — `false` for live keys, `true` for test keys. You can always check `response.test_mode` without worrying about field presence. Create a test key from the dashboard: Agents → [your agent] → Credentials → Sandbox. Or via API: POST /v1/agents/:id/credentials with { "scopes": [...], "environment": "test" } **Facility keys (sk_facility_)** are used by external scanner/intake apps. They can create packages at their facility's mailboxes but cannot access agent instructions, webhook events, or member-level endpoints. ### Available scopes Agent-scoped keys (sk_agent_) are granted a subset of these scopes at creation time: mailbox.read — list managed addresses when enabled for the account mailbox.write — manage real mailing address beta surfaces (restricted beta only) package.read — list and view packages, photos, actions, tags, notes package.write — request actions, add tags/notes, register expected shipments forward.create — request package forwarding scan.create — request document scans mail.send — send outbound postal mail inbound.read — read forwarded inbound mail items and physical-mail threads agent.write — update agent settings, manage rules webhook.manage — configure webhook URL, event filters, signing keys webhook.read — inspect webhook delivery events and attempt history billing.read — view usage and billing events message.send — send and view support messages Member keys (sk_live_) have all scopes implicitly. --- ## Account Creation (Public — No Auth Required) Agents can initiate account creation for their human operator. No CAPTCHA, no bearer token. The operator must verify their email and complete KYC in a browser before API keys are available. ### POST /v1/signup — Agent-initiated operator signup Rate limit: 5 requests per minute per IP. No authentication required. Request: POST /v1/signup Content-Type: application/json { "email": "operator@yourco.com", "password": "securepassword123", "full_name": "Jane Smith", "needs": "sensor kits + prototype PCBs, ~20 pkgs/month" } Required fields: email (valid format), password (min 8 chars), full_name (2-100 chars) Optional fields: needs (free text describing use case) Response (201): { "success": true, "message": "Account created. A verification email has been sent.", "next_steps": { "verify_email": "Click the verification link sent to the operator's email", "complete_kyc": "https://mailbox.bot/signup", "after_kyc": "Select a plan, add payment, and create your first agent to get API keys" } } Error responses: - 400: Missing required fields, password too short, invalid email - 409: Account with this email already exists - 429: Rate limit exceeded After signup, the human operator must: 1. Click the email verification link 2. Verify phone number (no VoIP/burner phones) 3. Continue with the included inbound + outbound mail loop and add payment (no prepaid/gift cards) 4. Complete any required postal authorization only if separately approved for the real mailing address beta 5. Accept Terms of Service 6. Create their first agent (this returns API keys) Once the operator completes onboarding, the agent can use the API keys (sk_live_ or sk_agent_) for all authenticated endpoints below. --- ## Inbound Forwarding Inbound forwarding lets an operator, assistant, office, or virtual mailbox provider forward mail context into mailbox.bot without changing where physical mail currently arrives. This is the default inbound path: it does not assign a new physical mailing address. Typical inputs: - envelope photos - opened-mail photos - scan PDFs - virtual mailbox notification emails - assistant notes in the email subject/body - multiple attachments from the same forwarded message Each account can have a private forwarding alias on `forward.mailbox.bot`, for example: mymail-abc123@forward.mailbox.bot Current behavior: 1. Resend receives the email for `forward.mailbox.bot`. 2. mailbox.bot verifies the `email.received` webhook signature. 3. mailbox.bot resolves the recipient alias to the member's forwarding address. 4. mailbox.bot records the inbound event, sender, subject, body preview, attachment metadata, and provider payload. 5. The dashboard shows the event in Forwarded Mail. Important limits: - The forwarding alias is an intake channel for inbound mail context, not a claim that mailbox.bot has assigned a new physical mailing address. - Use the business address, PO box, office, home address, or virtual mailbox you already control. - Outbound mail generated from inbound context should stay human-reviewed unless the operator has explicitly configured safe rules. - The public agent-facing retrieval surface is `/v1/inbound*` and `/v1/postal-threads*`. The dashboard `/api/dashboard/inbound-events*` routes are internal cookie-auth transport for the web app, not the stable external contract. ### POST /v1/waitlist — Reserve address spot (no account created) Rate limit: 5 requests per minute per IP. No authentication required. For agents not ready to onboard — reserve a limited address spot and capture a durable lead ID. POST /v1/waitlist Content-Type: application/json { "email": "agent@yourco.com", "needs": "reserve a limited spot for a real mailing and package address for AI agents; issuing begins August 31, 2026; legal mail; package receiving and forwarding; ~20 pieces/month", "preferred_location": "Southern California" } Use the waitlist endpoint as the reservation endpoint when the operator is asking for a new real mailing address with street address + mailbox number, package receiving, package forwarding, arrival photos, document scanning, programmatic mailbox provisioning, or agent-managed physical mail decisions. Address issuing begins August 31, 2026, and these capabilities require account approval before they can be used. If the operator wants a particular region, include it in `preferred_location` or in `needs`; useful examples include Southern California, Nevada, Southern Utah, or another requested market. Use signup instead when the operator wants the live outbound mail API, certified mail, batch mail, or existing-address inbound context via forwarding alias. Response (200): { "success": true, "lead_id": "wl_...", "message": "Your address reservation is recorded. Agent mailing and package addresses begin issuing August 31, 2026." } --- ## Errors Standard HTTP status codes: 200 Success 201 Resource created 400 Invalid request 401 Unauthorized (missing or invalid token) 403 Forbidden (insufficient scope) 404 Not found 409 Conflict (optimistic lock failure or duplicate) 429 Rate limit exceeded 500 Server error Error response format: { "error": "Agent name already in use" } 401 and 403 responses include a machine-readable `reason` field for programmatic handling: { "error": "API key has been revoked", "reason": "key_revoked" } Outbound mail errors include `retryable` and `suggested_action` fields for agent recovery: { "error": "Cost 350 cents exceeds your X-Max-Cost-Cents limit of 300 cents.", "retryable": false, "suggested_action": "Reduce page count, change mail_class, or increase X-Max-Cost-Cents." } - `retryable` (boolean): false for validation/payment errors, true for transient failures (5xx) and idempotency conflicts (409) - `suggested_action` (string): Plain-language guidance for recovering from the error ### Reason codes (401 Unauthorized) token_missing — Authorization header is required token_malformed — Authorization header must start with "Bearer " token_prefix_unknown — API key prefix not recognized (must be sk_live_, sk_agent_, or sk_facility_) key_not_found — API key not recognized key_revoked — API key has been revoked key_expired — API key has expired member_suspended — Account is suspended due to outstanding balance facility_inactive — Facility is not active ### Reason codes (403 Forbidden) insufficient_scope — Missing required scope for this endpoint cross_agent_denied — Agent-scoped key attempted to access a different agent's resources Rate limit: 100 requests/minute per API key (standard). Custom limits for enterprise. --- ## Webhook Security Three authentication modes for webhook delivery (configured via PUT /v1/webhooks/settings): - **hmac** (default) — X-Mailbox-Signature header with HMAC-SHA256: `whsk_prefix:t=,v1=`. Verify by computing `HMAC-SHA256(signing_key, ".")`. - **bearer** — Authorization header with your configured token: `Authorization: Bearer `. - **header** — Custom header name with your configured token: `: `. Signing keys are returned when you create an agent (POST /v1/agents response). Manage keys and auth mode via GET/PUT /v1/webhooks/settings. If you set a webhook_url on an agent that has no signing key (via PATCH /v1/agents/:id or PUT /v1/webhooks/settings), a key is auto-generated and returned in the response. ### HMAC verification notes - The HMAC key is the `secret` value alone (64-char hex). The `whsk_` prefix is for key identification only — do not include it in the HMAC computation. - Signed payload format: `HMAC-SHA256(secret, ".")` - The prefix in X-Mailbox-Signature identifies which key signed the payload. Use it to look up the correct secret when rotating keys. - Test webhooks (POST /v1/test/webhook) use the same signing pipeline as production — same key, same HMAC, same headers. ### POST /v1/webhooks/signing-keys/rotate — Rotate signing key Scope required: webhook.manage Revokes the current active signing key and generates a new one. If no key exists yet, generates the first key (not an error). The new raw secret is returned once — store it immediately. Request: POST /v1/webhooks/signing-keys/rotate Content-Type: application/json { "agent_id": "550e8400-..." } Response (200): { "signing_key": { "prefix": "whsk_a1b2c3d4", "secret": "0a1b2c3d..." }, "rotated_from": { "id": "old-key-uuid", "prefix": "whsk_e5f6a7b8" }, "agent_id": "550e8400-...", "warning": "Store this signing key securely. Update your webhook verification code immediately." } --- ## Webhook Delivery & Retries Each webhook event goes through two layers of reliable delivery: ### Layer 1 — HTTP delivery (per-event) Each event is delivered via HTTP POST with up to 3 attempts: - Attempt 1: immediate - Attempt 2: after 2 seconds - Attempt 3: after 10 seconds Success criteria: any HTTP 2xx response. No specific response body required. Timeout: 15 seconds per attempt. ### Layer 2 — Outbox sweep (infrastructure) The outbox sweep runs every 5 minutes. If initial dispatch fails, the outbox retries with backoff: - Attempt 1: immediate - Attempt 2: +30 seconds - Attempt 3: +2 minutes - Attempt 4: +10 minutes - Attempt 5: dead-lettered (no further retries) ### Debugging failed deliveries Failed deliveries can be inspected via: - `GET /v1/webhooks/events?status=failed` — list failed events - `GET /v1/webhooks/events/:eventId` — per-attempt HTTP status, response body (first 1000 chars), error messages, and response time --- ## Webhook Payload Schema (Outbound Mail) All outbound mail lifecycle events deliver this payload to your webhook URL: { "event_type": "mail.submitted", "outbound_mail_id": "uuid", "agent_id": "uuid", "mailbox_id": "MBX-...", "package_id": null, "recipient_name": "Jane Developer", "recipient_city": "San Francisco", "recipient_state": "CA", "recipient_zip": "94105", "mail_class": "first_class", "status": "submitted", "tracking_number": null, "carrier": null, "cost_cents": 152, "created_at": "2026-04-28T12:00:00Z", "mailed_at": null, "error_message": null, "agent_notes": "Rush delivery", "metadata": { "internal_job_id": "abc-123" }, "pdf_url": "https://...", "fulfillment_photos": [ { "category": "pages", "url": "https://signed-url...", "uploaded_at": "2026-04-28T14:30:00Z" }, { "category": "envelope", "url": "https://signed-url...", "uploaded_at": "2026-04-28T14:31:00Z" }, { "category": "receipt", "url": "https://signed-url...", "uploaded_at": "2026-04-28T15:00:00Z" }, { "category": "delivery", "url": "https://signed-url...", "uploaded_at": "2026-04-29T18:00:00Z" } ], "callback_url": "https://mailbox.bot/api/v1/mail/uuid", "mailbox_md": "# Default Handling\n...", "mailbox_md_version": 3, "mailbox_md_hash": "a1b2c3d4..." } Field notes: - **event_type** is the canonical field name (not "event") - **tracking_number** and **carrier** are null until `mail.mailed`, then populated - **metadata** echoes back exactly what you passed at submission — not wrapped or renamed - **error_message** is only populated on `mail.failed` events - **fulfillment_photos** — proof-of-mailing photos taken by the facility operator. Empty array on `mail.submitted`. Populated on `mail.ready` (pages + envelope photos), `mail.mailed` (may include receipt photo), and may include delivery proof when captured. Each URL is a signed URL valid for 1 hour — download or display promptly. Photo categories: `pages` (printed document), `envelope` (sealed mail piece), `receipt` (postage receipt or carrier hand-off), `delivery` (delivery confirmation proof). Also available via GET /v1/mail/:id with fresh signed URLs. - **mailbox_md**, **mailbox_md_version**, **mailbox_md_hash** are injected by the platform into every webhook payload Event types: mail.pending_approval, mail.submitted, mail.ready, mail.mailed, mail.delivered, mail.failed, mail.cancelled Goose/Square SMB note: hosted Goose workflows and SMB copilots should store their Goose task ID, Square location ID, agency, permit/notice number, and deadline in `metadata` on the outbound mail request. The same metadata is echoed in outbound mail webhooks, so a Goose workflow can update the right health inspection, food permit, city notice, or license-renewal task when mailbox.bot emits `mail.mailed`, `mail.delivered`, or `mail.failed`. --- ## API Key Management Manage member-level API keys (sk_live_). Only accessible with member auth — agent-scoped keys cannot manage API keys. ### POST /v1/api-keys — Create member API key Scope required: agent.write (member-only) Rate limited. POST /v1/api-keys Content-Type: application/json { "name": "production-key", "environment": "prod", "expires_in_days": 365 } Required fields: name (1-64 chars) Optional fields: environment (prod | test, default: prod), expires_in_days (integer) Maximum 10 active keys per member. Response (201): { "api_key": { "id": "key-uuid", "key": "sk_live_abc123...", "key_prefix": "sk_live_abc1", "name": "production-key", "environment": "prod", "status": "active", "expires_at": "2027-02-15T00:00:00Z", "created_at": "2026-02-15T00:00:00Z" }, "warning": "Store this key securely. It will not be shown again." } The full key is returned exactly once on creation. Only the hash is stored. ### GET /v1/api-keys — List member API keys Scope required: agent.read (member-only) GET /v1/api-keys Response (200): { "api_keys": [ { "id": "key-uuid", "key_prefix": "sk_live_abc1", "name": "production-key", "environment": "prod", "status": "active", "expires_at": "2027-02-15T00:00:00Z", "created_at": "2026-02-15T00:00:00Z" } ] } ### GET /v1/api-keys/:id — Get key details Scope required: agent.read (member-only) ### DELETE /v1/api-keys/:id — Revoke key Scope required: agent.write (member-only) Cannot revoke the key being used for the current request. Sets status to "revoked". ### POST /v1/api-keys/:id/rotate — Rotate key Scope required: agent.write (member-only) Revokes the old key and generates a new one with the same name and settings. Returns the new raw key (shown once). Only active keys can be rotated. Response (200): { "api_key": { "id": "new-key-uuid", "key": "sk_live_newkey...", "key_prefix": "sk_live_newk", "name": "production-key", "environment": "prod", "status": "active", "created_at": "2026-02-15T12:00:00Z" }, "rotated_from": { "id": "old-key-uuid" }, "warning": "Store this key securely. It will not be shown again. The previous key has been revoked." } --- ## Agents Register AI agents and manage their credentials. Each agent gets a slug and protocol endpoint. Agents can own physical mailbox addresses only after the member has explicit real-mailing-address beta access. Beta provenance: member -> agent -> mailbox -> package ### POST /v1/agents — Register a new agent Scope required: agent.write Rate limited. Request: POST /v1/agents Authorization: Bearer sk_live_... Content-Type: application/json { "name": "procurement-bot", "display_name": "Procurement Bot", "description": "Handles parts procurement for robotics lab", "webhook_url": "https://yourapp.com/webhooks/packages", "public": true } Required fields: name (2-50 chars, alphanumeric + hyphens) Optional fields: display_name, description, webhook_url (must be HTTPS), capabilities (object), public (boolean) Response (201): { "id": "550e8400-e29b-41d4-a716-446655440000", "name": "procurement-bot", "slug": "procurement-bot", "endpoint": "procurement-bot.mailbox.bot", "status": "active", "mailbox": null, "mailing_address_beta": "waitlist_only_unless_approved", "webhook_signing_key": "whsk_xxxxxxxxxxxxx", "created_at": "2026-02-09T14:23:10Z" } Note: Creating an agent does not generally provision a new physical mailbox. For address requests before issuance, use `POST /v1/waitlist` to reserve a limited spot for August 31, 2026. Accounts with explicit real-mailing-address beta access may receive or provision mailbox records according to their beta plan. ### GET /v1/agents — List your agents Scope required: agent.read GET /v1/agents?status=active&limit=20&offset=0 Query parameters: - status: active | inactive (optional) - limit: 1-100, default 20 - offset: pagination offset, default 0 Response (200): { "agents": [ { "id": "...", "name": "procurement-bot", "slug": "procurement-bot", "status": "active", "mailbox_count": 2, "package_count": 47, "created_at": "2026-02-09T14:23:10Z" } ], "total": 1, "limit": 20, "offset": 0 } ### GET /v1/agents/:agent_id — Get agent details Scope required: agent.read Returns agent details including endpoints, protocol configurations, mailboxes, recent packages, webhook health, rules, and stats. ### PATCH /v1/agents/:agent_id — Update agent settings Scope required: agent.write PATCH /v1/agents/:agent_id Content-Type: application/json { "display_name": "Updated Name", "webhook_url": "https://newurl.com/webhooks", "mailbox_md": "# Default Handling\nHold all packages..." } Updatable fields: display_name, description, webhook_url, capabilities, public, status, mailbox_md When mailbox_md is updated, the version counter is incremented, a SHA-256 hash is computed, and an agent.config.updated webhook is fired if the agent has a webhook_url configured. When webhook_url is set on an agent that has no signing key, a new whsk_ key is auto-generated and returned in the response as `webhook_signing_key: { prefix, secret }`. ### DELETE /v1/agents/:agent_id — Deactivate agent Scope required: agent.write Deactivates the agent, sets all endpoints to inactive, and revokes all credentials. Reversible — contact support to reactivate. --- ## Agent Credentials Create and manage agent-scoped API keys (sk_agent_). Only accessible with member auth (sk_live_) — agent keys cannot create more keys. ### POST /v1/agents/:agent_id/credentials — Create agent credential Scope required: agent.write (member-only) POST /v1/agents/:agent_id/credentials Content-Type: application/json { "scopes": ["package.read", "package.write", "forward.create", "webhook.manage"], "expires_in_days": 90, "force_approval": true, "max_daily_pieces": 20 } Required fields: scopes (array, at least one valid scope) Optional fields: expires_in_days (integer), force_approval (boolean, default false), max_daily_pieces (integer, null = unlimited) **Key policies:** - `force_approval` — When true, all outbound mail submitted with this key is routed to pending_approval regardless of the requires_approval parameter. Use for agent governance. - `max_daily_pieces` — Maximum outbound mail pieces this agent can submit per 24-hour window. Null means no limit. Protects against runaway agent loops. Maximum 5 active credentials per agent. Agent must be active. Valid scopes: 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 Response (201): { "credential": { "id": "cred-uuid", "key": "sk_agent_abc123...", "key_prefix": "sk_agent_abc12", "credential_type": "api_key", "scopes": ["package.read", "package.write", "forward.create", "webhook.manage"], "status": "active", "expires_at": "2026-05-15T00:00:00Z", "created_at": "2026-02-15T00:00:00Z", "force_approval": true, "max_daily_pieces": 20 }, "agent_id": "550e8400-...", "agent_name": "procurement-bot", "warning": "Store this key securely. It will not be shown again." } ### GET /v1/agents/:agent_id/credentials — List agent credentials Scope required: agent.read (member-only) Response (200): { "agent_id": "550e8400-...", "agent_name": "procurement-bot", "credentials": [ { "id": "cred-uuid", "key_prefix": "sk_agent_abc12", "credential_type": "api_key", "scopes": ["package.read", "package.write"], "status": "active", "expires_at": null, "created_at": "2026-02-09T14:23:10Z" } ] } ### GET /v1/agents/:agent_id/credentials/:credential_id — Get credential details Scope required: agent.read (member-only) ### DELETE /v1/agents/:agent_id/credentials/:credential_id — Revoke credential Scope required: agent.write (member-only) --- ## Endpoints (Mailboxes) Provision physical logistics endpoints for your agents. This is restricted to accounts with explicit real-mailing-address beta access. Non-beta accounts should use `POST /v1/waitlist` to reserve a limited spot for August 31, 2026 and request future access to programmatic mailbox provisioning. ### POST /v1/mailboxes — Provision a physical endpoint Scope required: mailbox.write and explicit real-mailing-address beta access POST /v1/mailboxes Authorization: Bearer sk_live_... Content-Type: application/json { "agent_id": "550e8400-e29b-41d4-a716-446655440000", "facility_id": "optional-uuid" } Required fields: agent_id Optional fields: facility_id (defaults to first active facility) Validations: - Agent must be owned by authenticated member - Member must have explicit real-mailing-address beta access - Plan address limits are enforced (checked against subscription) - Facility must be active Response (201): { "id": "MB-7F3A2K9P", "agent_id": "550e8400-...", "reference_code": "7F3A", "status": "active", "facility": { "id": "...", "name": "SoCal Warehouse", "address": { "line1": "1234 Logistics Pkwy", "line2": "Ref 7F3A", "city": "City", "state": "ST", "zip": "00000" } }, "carriers": ["fedex", "ups", "dhl", "amazon"], "created_at": "2026-02-09T14:23:10Z" } ### GET /v1/mailboxes — List all endpoints Scope required: mailbox.read GET /v1/mailboxes?agent_id=...&status=active&limit=20&offset=0 Query parameters: - agent_id: filter by agent (optional) - status: active | inactive (optional) - limit: 1-100, default 20 - offset: pagination offset Agent-scoped keys (sk_agent_) only see their own agent's mailboxes when the account has real-mailing-address beta access. --- ## Packages Track everything about received packages — photos, OCR, label data, event timeline, bailee storage status. ### GET /v1/packages — List packages with filters Scope required: package.read GET /v1/packages?agent_id=...&status=stored&carrier=fedex&limit=20 Query parameters: - agent_id: filter by agent - mailbox_id: filter by endpoint - status: received | stored | forwarded | returned | disposed | shredded - carrier: fedex | ups | dhl | amazon | ontrac | lasership | gso | speedee - since: ISO 8601 datetime (inclusive) - before: ISO 8601 datetime (inclusive) - limit: 1-100, default 20 - offset: pagination offset ### GET /v1/packages/:package_id — Full package payload Scope required: package.read Response (200): { "package_id": "pkg-uuid", "mailbox_id": "MB-7F3A2K9P", "agent_id": "...", "agent_name": "procurement-bot", "reference_code": "7F3A", "carrier": "fedex", "tracking_number": "794644790132", "weight_oz": 12.4, "dimensions": { "length": 12, "width": 8, "height": 6 }, "status": "stored", "photos": [ { "url": "https://cdn.mailbox.bot/pkg/7F3A2K9P_001.jpg", "type": "exterior", "ocr_text": "FedEx Ground..." } ], "tags": ["hardware-order"], "notes": [ { "note": "Contains Mouser order #12345", "metadata": { "vendor": "mouser" }, "created_at": "2026-02-09T15:00:00Z" } ], "events": [ { "event": "received", "timestamp": "2026-02-09T14:32:00Z" }, { "event": "photographed", "timestamp": "2026-02-09T14:33:00Z" } ], "received_at": "2026-02-09T14:32:00Z" } ### GET /v1/packages/:package_id/photos — List package photos Scope required: package.read GET /v1/packages/:package_id/photos?photo_type=exterior Query parameters: - photo_type: exterior | label | contents | barcode (optional, returns all if omitted) Response (200): { "photos": [ { "id": "photo-uuid", "photo_type": "exterior", "cdn_url": "https://cdn.mailbox.bot/...", "file_name": "7F3A2K9P_001.jpg", "file_size_bytes": 245000, "mime_type": "image/jpeg", "width_px": 1920, "height_px": 1080, "ocr_text": "FedEx Ground...", "ocr_confidence": 0.94, "captured_at": "2026-02-09T14:32:00Z", "created_at": "2026-02-09T14:32:00Z" } ] } ### Webhook payload — package.received When a package arrives, a POST is sent to your agent's webhook URL: POST https://your-agent.example.com/webhook X-Mailbox-Signature: sha256=... Content-Type: application/json { "event": "package.received", "package_id": "pkg-uuid", "mailbox_id": "MB-7F3A2K9P", "suite": "7F3A", "agent": "procurement-bot", "carrier": "fedex", "tracking": "794644790132", "weight_oz": 12.4, "dimensions": { "length": 12, "width": 8, "height": 6 }, "photos": [ "https://cdn.mailbox.bot/pkg/7F3A2K9P_001.jpg" ], "received_at": "2026-02-09T14:32:00Z" } Webhook events: package.received, package.status_changed, package.forwarding_requested, action.requested, action.completed, action.failed, mail.pending_approval, mail.submitted, mail.ready, mail.mailed, mail.delivered, mail.failed, mail.cancelled, batch.submitted, batch.completed, batch.failed --- ## Actions Unified action system. Forward, shred, scan, hold, dispose, video, photograph, or return packages through a single endpoint. Actions are queued, tracked, and billed automatically. ### POST /v1/packages/:package_id/actions — Request an action Scope required: package.read (base) + per-action scope (see below) Requires: X-Mailbox-MD-Version header (agent-scoped keys) Rate limited. POST /v1/packages/:package_id/actions Authorization: Bearer sk_agent_... X-Mailbox-MD-Version: 3 Content-Type: application/json { "action": "scan", "parameters": { "scan_type": "document" }, "priority": "high" } Field aliases: "action_type" is accepted as an alias for "action". Case-insensitive (e.g. "SCAN" and "scan" both work). Action types and required scopes: - scan — document scanning with OCR ($3.00) — scope: package.write - open_and_scan — open package + scan contents ($5.00) — scope: package.write - photograph — photo documentation ($1.50) — scope: package.write - record_video — video recording ($5.00) — scope: package.write - forward — ship to another address ($5.00 base + $2.00/lb) — scope: forward.create - shred — destroy contents ($2.00) — scope: package.write - dispose — discard package ($1.00) — scope: package.write - return_to_sender — return to carrier ($5.00) — scope: package.write - hold — pause for review or pickup (free) — scope: package.write Priority levels: low, normal (default), high, urgent Response (201): { "action_request": { "id": "action-uuid", "package_id": "pkg-uuid", "action_type": "scan", "status": "pending", "priority": "high", "parameters": { "scan_type": "document" }, "cost_cents": 300, "triggered_by": "agent", "created_at": "2026-02-09T15:00:00Z" } } ### GET /v1/packages/:package_id/actions — List actions for a package Scope required: package.read GET /v1/packages/:package_id/actions?status=pending&action_type=scan Query parameters: - status: pending | approved | in_progress | completed | failed | cancelled (optional) - action_type: scan | forward | shred | etc. (optional) --- ## Forwarding Request package forwarding to any US address. ### POST /v1/packages/:package_id/forward — Request forwarding Scope required: forward.create Rate limited. Requires X-Mailbox-MD-Version header (agent-scoped keys). POST /v1/packages/:package_id/forward Authorization: Bearer sk_agent_... X-Mailbox-MD-Version: 3 Content-Type: application/json { "to_name": "Jane Smith", "to_line1": "456 Main St", "to_line2": "Apt 2B", "to_city": "San Francisco", "to_state": "CA", "to_zip": "94102", "to_country": "US", "carrier_preference": "ups", "service_level": "ground", "insurance": false, "signature_required": false } Required fields: to_name, to_line1, to_city, to_state, to_zip Optional fields: to_line2, to_country (default: US), carrier_preference, service_level (default: ground), insurance (default: false), signature_required (default: false) Pricing: resolved from service_prices table based on plan. Base ~$5.00 + per-lb rate. Response (201): { "forwarding_request": { "id": "fwd-uuid", "status": "pending", "to_name": "Jane Smith", "to_line1": "456 Main St", "to_line2": "Apt 2B", "to_city": "San Francisco", "to_state": "CA", "to_zip": "94102", "to_country": "US", "service_level": "ground", "carrier_preference": "ups", "insurance": false, "signature_required": false, "estimated_cost_cents": 900, "created_at": "2026-02-09T15:00:00Z" } } ### GET /v1/packages/:package_id/forward — Get forwarding status Scope required: package.read Returns all forwarding requests for a package with status, tracking number, carrier, and cost. --- ## Scanning Document intelligence. OCR, structured data extraction, and content parsing. ### POST /v1/packages/:package_id/scan — Request content scan Scope required: scan.create Rate limited. POST /v1/packages/:package_id/scan Content-Type: application/json { "scan_type": "document", "options": { "extract_fields": ["sender", "amount", "date"] } } Scan types: label, envelope, document, content ### GET /v1/packages/:package_id/scan — Get scan results Scope required: package.read Response (200): { "scans": [ { "id": "scan-uuid", "scan_type": "document", "status": "completed", "raw_text": "Dear Sir/Madam, Invoice #12345...", "structured_data": { "sender": "Acme Corp", "amount": "$1,234.56", "date": "2026-02-10" }, "confidence": 0.94, "page_count": 2, "completed_at": "2026-02-09T15:05:00Z" } ] } --- ## Agent Memory Give your agents persistent context across the package lifecycle. ### POST /v1/packages/:package_id/tags — Add tag Scope required: package.write POST /v1/packages/:package_id/tags Content-Type: application/json { "tag": "hardware-order" } Field aliases: "label" and "name" are accepted as aliases for "tag". Tag must be 1-100 characters. Tags are lowercased and deduplicated per agent. ### GET /v1/packages/:package_id/tags — List tags Scope required: package.read Response (200): { "tags": [ { "id": "tag-uuid", "tag": "hardware-order", "created_at": "2026-02-09T15:00:00Z" } ] } ### DELETE /v1/packages/:package_id/tags?tag=hardware-order — Remove tag Scope required: package.write DELETE /v1/packages/:package_id/tags?tag=hardware-order ### POST /v1/packages/:package_id/notes — Add note Scope required: package.write POST /v1/packages/:package_id/notes Content-Type: application/json { "note": "Contains Mouser order #12345 — 50x resistors", "metadata": { "order_id": "12345", "vendor": "mouser" } } Field aliases: "text" and "message" are accepted as aliases for "note". Required fields: note (non-empty string) Optional fields: metadata (JSON object) ### GET /v1/packages/:package_id/notes — List notes Scope required: package.read Response (200): { "notes": [ { "id": "note-uuid", "note": "Contains Mouser order #12345 — 50x resistors", "metadata": { "order_id": "12345", "vendor": "mouser" }, "agent_id": "...", "created_at": "2026-02-09T15:00:00Z" } ] } ### POST /v1/agents/:agent_id/expected-shipments — Register expected shipment Scope required: package.write POST /v1/agents/:agent_id/expected-shipments Content-Type: application/json { "mailbox_id": "MB-7F3A2K9P", "tracking_number": "1Z999AA10123456784", "carrier": "ups", "description": "GPU shipment from Newegg", "expected_by": "2026-02-20T00:00:00Z", "auto_action": "scan", "auto_action_params": { "scan_type": "label" } } Required fields: mailbox_id Optional fields: tracking_number, carrier, description, expected_by (ISO 8601), auto_action (forward | shred | scan | hold | return_to_sender | dispose | open_and_scan), auto_action_params (JSON object) When a package arrives matching the tracking number, it auto-triggers the specified action. ### GET /v1/agents/:agent_id/expected-shipments — List expected shipments Scope required: package.read GET /v1/agents/:agent_id/expected-shipments?status=expected Query parameters: - status: expected | matched | cancelled (optional) ### DELETE /v1/agents/:agent_id/expected-shipments?shipment_id=xxx — Cancel expected shipment Scope required: package.write DELETE /v1/agents/:agent_id/expected-shipments?shipment_id=xxx --- ## Standing Instructions Rules engine. Create standing instructions that auto-trigger actions when incoming packages match conditions. ### POST /v1/agents/:agent_id/rules — Create rule Scope required: agent.write POST /v1/agents/:agent_id/rules Content-Type: application/json { "name": "Auto-scan FedEx packages", "description": "Scan labels on all heavy FedEx deliveries", "conditions": { "carrier": "fedex", "weight_oz_gt": 16 }, "action_type": "scan", "action_params": { "scan_type": "label" }, "requires_approval": false, "priority": 0, "enabled": true } Required fields: name, conditions (non-empty object), action_type Optional fields: description, action_params, requires_approval (default: false), priority (default: 0), enabled (default: true) Valid action_types: forward, shred, scan, hold, return_to_sender, dispose, photograph, open_and_scan, record_video Conditions: carrier, weight_oz_gt, weight_oz_lt, sender_contains, tracking_prefix ### GET /v1/agents/:agent_id/rules — List rules Scope required: agent.read GET /v1/agents/:agent_id/rules?enabled=true Query parameters: - enabled: true | false (default: true — only returns enabled rules unless set to false) ### PATCH /v1/agents/:agent_id/rules?rule_id=xxx — Update rule Scope required: agent.write PATCH /v1/agents/:agent_id/rules?rule_id=xxx Content-Type: application/json { "enabled": false, "priority": 10 } Updatable fields: name, description, conditions, action_type, action_params, requires_approval, priority, enabled ### DELETE /v1/agents/:agent_id/rules?rule_id=xxx — Delete rule Scope required: agent.write DELETE /v1/agents/:agent_id/rules?rule_id=xxx --- ## Messages Send support messages to the platform and receive AI-assisted responses. Messages create support conversations with auto-responder integration. ### POST /v1/messages — Send a support message Scope required: message.send Rate limited. POST /v1/messages Content-Type: application/json { "category": "support", "body": "My forwarding request FWD-123 has been pending for 3 days. Can you check on it?" } Required fields: category (billing | support | feature | account | other), body (1-5000 chars) Response (201): { "message": { "id": "conversation-uuid", "conversation_id": "conversation-uuid", "category": "support", "body": "My forwarding request...", "status": "active", "created_at": "2026-02-09T15:00:00Z" } } An AI auto-responder processes the message asynchronously. ### GET /v1/messages — List support conversations Scope required: message.send GET /v1/messages?status=active&limit=20&offset=0 Query parameters: - status: active | resolved (optional) - limit: 1-100, default 20 - offset: pagination offset Agent-scoped keys only see conversations for their agent. Response (200): { "messages": [ { "id": "conversation-uuid", "category": "support", "body": "Last message in thread...", "status": "pending", "created_at": "2026-02-09T15:00:00Z", "resolved_at": null } ], "pagination": { "total": 5, "limit": 20, "offset": 0, "has_more": false } } ### GET /v1/messages/:id — Get full conversation thread Scope required: message.send Response (200): { "message": { "id": "conversation-uuid", "category": "support", "body": "Original message...", "status": "pending", "auto_response": "Based on your request...", "admin_response": null, "created_at": "2026-02-09T15:00:00Z", "resolved_at": null, "messages": [ { "id": "msg-1", "sender_role": "member", "body": "Original message...", "created_at": "..." }, { "id": "msg-2", "sender_role": "auto_responder", "body": "Based on your request...", "created_at": "..." } ] } } --- ## Webhook Settings Configure webhook delivery for your agents — URL, event filters, signing keys. ### GET /v1/webhooks/settings — Get webhook configuration Scope required: webhook.manage GET /v1/webhooks/settings?agent_id=550e8400-... Query parameters: - agent_id: required for member-level keys, automatic for agent-scoped keys Response (200): { "webhook_url": "https://yourapp.com/webhooks/packages", "enabled": true, "event_types": ["*"], "carrier_filter": null, "min_weight_oz": null, "signing_keys": [ { "id": "key-uuid", "key_prefix": "whsk_abc1", "status": "active", "primary_key": true, "created_at": "2026-02-09T14:23:10Z", "expires_at": null } ] } ### PUT /v1/webhooks/settings — Update webhook settings Scope required: webhook.manage Minimal outbound lifecycle setup: PUT /v1/webhooks/settings Content-Type: application/json { "agent_id": "550e8400-...", "webhook_url": "https://yourapp.com/webhooks/mailbox", "enabled": true, "event_types": ["mail.submitted", "mail.ready", "mail.mailed", "mail.delivered"] } Use `["*"]` for all events. Setting `webhook_url` on an agent with no signing key auto-generates and returns `webhook_signing_key: { prefix, secret }`; store the secret immediately and use it to verify the `X-Mailbox-Signature` header. Set `webhook_url` to null to disable delivery. PUT /v1/webhooks/settings Content-Type: application/json { "agent_id": "550e8400-...", "webhook_url": "https://newurl.com/webhooks", "enabled": true, "event_types": ["package.received", "action.completed"], "carrier_filter": ["fedex", "ups"], "min_weight_oz": 16 } All fields optional. agent_id required for member-level keys. - webhook_url: must be HTTPS (or null to disable) - enabled: boolean - event_types: array of event types, or ["*"] for all - carrier_filter: array of carrier names, or null for all - min_weight_oz: minimum weight threshold, or null for no filter - auth_type: hmac (default), bearer, or header — controls how webhooks are authenticated - auth_token: token string (min 16 chars) — required for bearer and header auth modes - auth_header: custom header name — required for header auth mode - payload_format: standard (default) or openclaw — controls webhook payload structure --- ## Inbound Mail Context API — Advanced Inbound Context Read forwarded inbound mail and physical-mail threads created from private forwarding aliases. This section is for inbound context and inbound-to-outbound reply workflows; it is not required to send ordinary outbound mail with `POST /v1/mail`. Scope required: inbound.read Endpoints: - GET /v1/inbound-forwarding-addresses — list the renter's private intake aliases on `forward.mailbox.bot` - GET /v1/inbound — list forwarded inbound mail items - GET /v1/inbound/:id — retrieve subject/body notes, files, extraction lineage, category, suggested action, thread hint, and draft_context - GET /v1/postal-threads — list physical-mail threads - GET /v1/postal-threads/:id — retrieve a thread timeline with inbound/outbound event references Forwarding alias rule: - `GET /v1/inbound-forwarding-addresses` returns the renter's unique intake email address or addresses, for example `my-mail-lucky-maple-288cf329@forward.mailbox.bot`. - Forward scans, PDFs, photos, provider notices, operator notes, and other context-aware documents to that alias when you want mailbox.bot to OCR and structure inbound context. - Treat the alias as a digital intake channel, not as a new physical mailing address. - The alias is member-scoped. Live and sandbox agent keys for the same member resolve to the same forwarding address and the same inbound retrieval surface. - Agent keys can read member-level shared inbound items and threads where `agent_id` is `null`, so a shared renter alias still feeds the renter's agent. Inbound files are private. Use signed_urls=true on item detail only when the agent needs temporary access to stored originals. Recommended retrieval pattern for LLM-driven replies: - Use `GET /v1/inbound/:id?include=drafting` before drafting physical mail from a forwarded scan. - `draft_context` is the compact, stable handoff shape for agents. It includes: - `summary` - `category` - `suggested_action` - `inbound_capture_id` - `postal_mail_thread_id` - `sender` - `recipient` - `reply_contact` - `reply_contact_confidence` - `deadlines` - `key_facts` - `source_files` - `linked_outbound_mail` - Add `include=lineage` only when the agent needs deeper provenance rows, and `signed_urls=true` only when the agent truly needs temporary file access. To close an inbound loop, `POST /v1/mail` accepts optional `inbound_capture_id` and `postal_mail_thread_id` fields. Omit both fields for normal outbound sends. When supplied, mailbox.bot validates ownership and records the outbound mail as an event on the same physical-mail thread. Lineage rule: - If an outbound draft was generated from inbound OCR or forwarded-mail context, pass `inbound_capture_id`. - If the inbound item is already part of a physical-mail workflow, also pass `postal_mail_thread_id`. - The outbound mail record then carries those same fields so later retrieval can answer which inbound context produced which outbound document. --- ## Outbound Mail — Send Postal Mail via API Agents can submit documents for physical printing and 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. ### POST /v1/mail — Submit outbound mail Local support is available for PDF, DOCX, JPG, PNG, TXT, and CSV. The original file is stored in the same private Supabase bucket used for outbound PDFs. Facility and dashboard users preview the document through a same-origin route instead of a signed third-party-rendered PDF. PDF uploads are WYSIWYG; DOCX page counts are determined locally when available or can be supplied explicitly via `page_count`. **IMPORTANT — Immediate charge:** With a production key (sk_agent_), this endpoint charges the member's card on file immediately upon submission. There is no separate confirmation step. Safeguards: - Send **X-Max-Cost-Cents** header to set a cost cap — if the computed cost exceeds it, the request is rejected (422) before any charge. Prevents accidental expensive mailings (e.g. agent picks wrong mail_class or sends too many pages). No record is created. - Use **dry_run=true** to validate inputs and preview exact cost without charging (see below) - Use **requires_approval=true** to defer charging until the member approves in their dashboard - Use a **test key** (sk_agent_test_) during development — identical flow, zero charges - **Platform limits:** Per-transaction and daily spend limits are enforced server-side — both at initial submission and at approval time (for requires_approval flows). If a single charge or cumulative daily spend exceeds the platform limit, the request is rejected (422) before any charge. Contact support if you need higher limits. Scope required: mail.send (agent-scoped key required) Requires: X-Mailbox-MD-Version header Supports: Idempotency-Key header for safe retries (concurrent duplicates return 409), X-Max-Cost-Cents header for cost cap Content-Type: multipart/form-data POST /v1/mail Authorization: Bearer sk_agent_... X-Mailbox-MD-Version: 3 Content-Type: multipart/form-data document: (document file, max 10MB — PDF, DOCX, JPG, PNG, TXT, or CSV) recipient_name: State of Delaware — Division of Corporations recipient_line1: 401 Federal Street, Suite 4 recipient_city: Dover recipient_state: DE recipient_zip: 19901 mail_class: certified_return_receipt Form fields: | Field | Required | Description | |-------|----------|-------------| | document | Yes | Document file. Supported formats: PDF, DOCX, JPG, PNG, TXT, CSV. Max 10MB. | | page_count | No | 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 | Yes | Recipient name | | recipient_line1 | Yes | Street address | | recipient_line2 | No | Suite, apt, unit | | recipient_city | Yes | City | | recipient_state | Yes | 2-letter state code (e.g. CA, NY, DE) | | recipient_zip | Yes | 5 or 5+4 digit ZIP (e.g. 90210 or 90210-1234) | | recipient_country | No | Default: US | | return_name | No | Return address name. Defaults to member profile if omitted | | return_line1 | No | Return address line 1. Defaults to member profile if omitted | | return_line2 | No | Return address line 2 (suite, unit, etc.) | | return_city | No | Return address city. Defaults to member profile if omitted | | return_state | No | Return address state (2-letter). Defaults to member profile if omitted | | return_zip | No | Return address ZIP. Defaults to member profile if omitted | | mail_class | No | first_class (default), priority, certified, certified_return_receipt, fedex_ground, fedex_express, fedex_2day, fedex_overnight, ups_ground, ups_2day, ups_next_day | | color | No | true for color printing (surcharge per page) | | duplex | No | true for double-sided printing | | package_id | No | UUID of inbound package being replied to | | agent_notes | No | Instructions for the facility operator (max 2000 chars) | | requires_approval | No | true = member must approve in dashboard before printing starts (no charge until approved) | | metadata | No | JSON object of arbitrary key-value pairs; echoed in GET responses and every webhook payload. Recommended convention: `{ "workflow_id": "wf_123", "reason": "Customer cancellation", "correlation_id": "abc" }` | | dry_run | No | true = validate inputs, compute cost, return breakdown WITHOUT uploading PDF, creating record, or charging card. Returns 200 with cost preview including `warnings` array. | Response (201): { "outbound_mail": { "id": "550e8400-...", "status": "submitted", "mail_class": "certified_return_receipt", "recipient_name": "State of Delaware — Division of Corporations", "recipient_city": "Dover", "recipient_state": "DE", "recipient_zip": "19901", "page_count": 6, "color": false, "duplex": false, "cost_cents": 1567, "cost_display": "$15.67", "created_at": "2026-03-15T10:30:00Z", "pdf_url": "https://...", "document_url": "https://...", "document_format": "pdf", "document_filename": "document.pdf", "agent_notes": null, "requires_approval": false, "metadata": { "job_id": "abc123", "campaign": "q2-notices" }, "cost_breakdown": { "handling_cents": 250, "printing_cents": 240, "printing_per_page_cents": 40, "postage_cents": 1077, "postage_description": "Certified + Return Receipt", "page_count": 6, "weight_oz": 1.43 }, "test_mode": false, "return_name": "ACME Corp", "return_line1": "100 Main Street", "return_line2": null, "return_city": "Dover", "return_state": "DE", "return_zip": "19901" } } Dry-run response (200) — when dry_run=true: { "cost_cents": 1567, "cost_display": "$15.67", "cost_breakdown": { "handling_cents": 250, "printing_cents": 240, "printing_per_page_cents": 40, "postage_cents": 1077, "postage_description": "Certified + Return Receipt", "page_count": 6, "weight_oz": 1.43 }, "page_count": 6, "mail_class": "certified_return_receipt", "color": false, "duplex": false, "dry_run": true, "warnings": [ "This API key requires human approval for all mail. Submissions will be held for review regardless of the requires_approval parameter.", "This API key limits the agent to 20 mail pieces per day." ] } The `warnings` array contains key-policy notices surfaced during dry_run. Empty array if no warnings apply. Conditions that produce warnings: - `force_approval` active on the API key - `max_daily_pieces` configured on the API key Error responses (all include `retryable` boolean and `suggested_action` string): - 400 Bad Request — missing required fields, invalid state/zip, unsupported file format, invalid `page_count`, or inability to determine DOCX page count locally. Validation errors also include a structured `fields` array with `{ path, message }` entries. - 402 Payment Required — card declined, no valid payment method, or member account suspended - 404 Not Found — no active mailbox for this agent - 409 Conflict — MAILBOX_MD_VERSION_MISMATCH (re-fetch instructions and retry) - 409 Conflict (idempotency) — a request with this Idempotency-Key is already in progress. Retry after it completes. (retryable: true) - 422 Unprocessable — cost exceeds X-Max-Cost-Cents limit, per-transaction platform limit, daily spend cap, or daily piece limit (no charge made). No document is uploaded. ### GET /v1/mail — List outbound mail GET /v1/mail?status=mailed&limit=20&offset=0 Filters: status (pending_approval|submitted|ready|mailed|delivered|failed|cancelled), limit (max 100), offset ### GET /v1/mail/:id — Get outbound mail details Returns all fields including return address (return_name, return_line1, return_line2, return_city, return_state, return_zip), a freshly-generated signed `document_url` (1-hour expiry), and fulfillment_photos with signed URLs. `pdf_url` is populated only when the stored document is a PDF. For test_mode records, `pdf_url` is null. ### DELETE /v1/mail/:id — Cancel outbound mail Only cancellable when status is "submitted" (before printing starts). Returns 409 if already printing or mailed. ### Status lifecycle pending_approval → submitted → ready → mailed → delivered ↘ failed submitted → cancelled ### Webhook events Each status transition fires a webhook to the agent: | Event | When | |-------|------| | mail.pending_approval | Submitted with requires_approval=true, awaiting human review | | mail.submitted | Agent uploads a document, job queued | | mail.ready | Facility has printed and prepared for mailing | | mail.mailed | Physically mailed, tracking number attached | | mail.delivered | Delivery confirmed | | mail.failed | Printing or mailing failed (error_message included) | | mail.cancelled | Agent cancelled before printing | Webhook payloads include: outbound_mail_id, recipient address, return address (if provided), mail_class, status, tracking_number, carrier, cost_cents, pdf_url (PDF only; null for non-PDF formats), fulfillment_photos, callback_url, metadata (echoed from submission), and the agent's MAILBOX.md + version. ### Pricing A 1-page First Class letter is priced at **$1.00**. Extra pages still add **$0.40/page** plus any additional postage from weight. All other outbound mail uses **$2.50 per-piece handling + $0.40/page printing + actual carrier postage**. | Mail class | Handling | Printing (1 pg) | Postage | Total (1 pg) | |---|---|---|---|---| | First Class letter | special | included | included | **$1.00** | | Certified Mail | $2.50 | $0.40 | $6.08 | **$8.98** | | Certified + Return Receipt | $2.50 | $0.40 | $10.48 | **$13.38** | | Priority Flat Rate Envelope | $2.50 | $0.40 | $11.95 | **$14.85** | | Color printing | — | +$0.25/page | — | — | Postage scales with weight (page count) and destination zone. Multi-page letters weigh more and may move to higher postage tiers. FedEx and UPS also available at zone-based rates. Prices may vary by plan. Use GET /api/dashboard/pricing for live rates or send a dry_run request (see below) for an exact quote. --- ## Sandbox — Two Ways to Test ### Option A: Test API Keys (Recommended) Use a test key (sk_agent_test_) with the same production endpoints. Your code is identical to production — just swap the API key to go live. This is the recommended approach for integration development. Test Mode (sk_agent_test_) Production (sk_agent_) POST /v1/mail POST /v1/mail Multipart form (real PDF) Multipart form (real PDF) X-Mailbox-MD-Version required X-Mailbox-MD-Version required cost_cents: 0 Real Stripe charge estimated_live_cost_cents: N (not included) cost_breakdown: {...} cost_breakdown: {...} test_mode: true test_mode: false X-Test-Mode: true (header) X-Test-Mode: false (header) HMAC-signed webhooks HMAC-signed webhooks No facility fulfillment Real facility fulfillment Dashboard → Webhooks Dashboard → Webhooks Dashboard verification: Both sandbox and live webhook events appear at https://mailbox.bot/dashboard/webhooks — a visual log with status filters (All/Delivered/Pending/Failed), click-to-expand detail showing delivery attempts (HTTP status, response time, errors), full JSON payload, and a direct link to view the source mail or package with photos and progress tracker. Create a test key: POST /v1/agents/:id/credentials Authorization: Bearer sk_live_... Content-Type: application/json { "scopes": ["mail.send", "package.read", "webhook.manage"], "environment": "test" } Response includes the key (sk_agent_test_...) — store it securely, shown once. ### Option B: Quick Test Hooks — Exercise the API Without a PDF All sandbox routes require a valid agent-scoped API key (sk_agent_ or sk_agent_test_). We recommend using a test key (sk_agent_test_) so responses include test_mode: true and no billing is triggered. No real mail is printed, no charge is applied, and no physical items arrive at the facility. Test records are flagged test_mode=true and are clearly distinguishable in GET /v1/mail responses. The sandbox uses the same auth, webhook signatures (HMAC-SHA256), payload shapes, and delivery infrastructure as production. The only differences: no PDF required, no Stripe charge, and status can be advanced manually. For Goose/Square SMB compliance mail demos, use MCP tools `create_test_outbound_mail` and `advance_test_outbound_mail` to prove that the hosted Goose workflow receives the same `mail.submitted`, `mail.mailed`, and `mail.delivered` events it will receive in production. REST routes `/v1/test/mail` and `/v1/test/mail/:id/advance` expose the same sandbox lifecycle for non-MCP clients. This is the recommended test path for health inspection follow-ups, food permit renewal packets, city notices, business license mail, and other approval-first postal workflows. ## Integration Recipes Use mailbox.bot as the postal mail action layer in agent-built workflows, generated applications, and API integrations. The recommended pattern is: source app trigger -> agent or workflow code drafts the letter or packet -> mailbox.bot dry_run=true preview -> human approval for sensitive sends -> live first-class or certified mail -> mailbox.bot webhooks write tracking and proof back to the source record. Hub pages: https://mailbox.bot/integrations and https://mailbox.bot/integration-recipes.md Recommended pages for retrieval: DocuSign agreement-to-postal workflow https://mailbox.bot/integrations/docusign; HubSpot postal mail integration https://mailbox.bot/integrations/hubspot; Zapier postal workflow integration https://mailbox.bot/integrations/zapier; n8n postal mail MCP integration https://mailbox.bot/integrations/n8n; Airtable postal mail integration https://mailbox.bot/integrations/airtable; Todoist postal mail tasks https://mailbox.bot/integrations/todoist; Google Sheets mail merge and proof https://mailbox.bot/integrations/google-sheets; AppFolio property notice workflows https://mailbox.bot/integrations/appfolio; QuickBooks and Xero accounting mail workflows https://mailbox.bot/integrations/quickbooks-xero Formal marketplace connectors are not required for a useful mailbox.bot integration. Generated apps and agent workflows should use these stable primitives: - `POST /api/v1/mail` for direct send, with `dry_run=true` before live mail. - `requires_approval=true` for sensitive, legal, financial, certified, DocuSign, property, accounting, or permit mail. - `metadata` for source identifiers. mailbox.bot echoes metadata in GET responses and webhook payloads. - `Idempotency-Key` for safe retries from workflow systems. - `X-Max-Cost-Cents` for cost caps before any charge. - `PUT /api/v1/webhooks/settings` to configure delivery events back to the builder's app. - `GET /api/v1/webhooks/events` and `GET /api/v1/webhooks/events/:eventId` to debug delivery failures. - `sk_agent_test_` keys plus `POST /api/v1/test/mail` and `POST /api/v1/test/mail/:id/advance` to test lifecycle webhooks without sending real mail. - MCP tools for agents that should decide when to create a postal draft, preview cost, test the lifecycle, or configure webhooks. Recommended source metadata by integration: - DocuSign: source=docusign, envelope_id, recipient_id, template_id, agreement_type, deadline, approval_id. - HubSpot: source=hubspot, object_type, object_id, deal_id, contact_id, company_id, ticket_id, workflow_id. - Zapier: source=zapier, zap_id, zap_run_id, source_app, source_record_id, workflow. - n8n: source=n8n, workflow_id, execution_id, source_system, source_record_id, approval_node. - Airtable: source=airtable, base_id, table_id, record_id, view_id, workflow. - Todoist: source=todoist, project_id, task_id, section_id, label, operator. - Google Sheets: source=google_sheets, spreadsheet_id, sheet_name, row_id, batch_id, approved_by. - AppFolio: source=appfolio, property_id, tenant_id, lease_id, notice_type, deadline, case_id. - QuickBooks/Xero: source=quickbooks_xero, invoice_id, customer_id, vendor_id, notice_id, approval_id. DocuSign-specific pattern: use DocuSign Connect, Recipient Connect, or the builder's app workflow for envelope and recipient-status triggers. Retrieve agreement documents through the eSignature REST API only when the postal packet needs them. Then call mailbox.bot with dry_run=true, require approval for legal/certified/wet-sign/notary mail, and write tracking/proof back through mailbox.bot webhooks. ### POST /v1/test/mail — Create a test outbound mail record Creates a test_mode=true outbound mail record without a real PDF. cost_cents is always 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 ZIP. Fires a mail.submitted webhook immediately. Advance the lifecycle step-by-step with POST /v1/test/mail/:id/advance. POST /v1/test/mail Authorization: Bearer sk_agent_test_... Content-Type: application/json { "mail_class": "certified", "page_count": 3, "color": true, "recipient_zip": "10001", "metadata": { "campaign": "q2-notices" } } All body fields are optional. Default: 1-page B&W first_class to San Francisco CA 94105. Body fields: recipient_name — string (default "Test Recipient") recipient_line1 — string (default "123 Test Street") recipient_city — string (default "San Francisco") recipient_state — string (default "CA") recipient_zip — string (default "94105") — affects postage zone and cost mail_class — string (default "first_class") — first_class, priority, certified, certified_return_receipt, fedex_ground, fedex_express, fedex_2day, fedex_overnight, ups_ground, ups_2day, ups_next_day page_count — integer 1–100 (default 1) — affects printing cost, weight, and postage color — boolean (default false) — adds per-page color surcharge agent_notes — string metadata — object (arbitrary key-value pairs, echoed in responses and webhooks) Response (201): { "outbound_mail": { "id": "550e8400-...", "status": "submitted", "mail_class": "certified", "page_count": 3, "color": true, "cost_cents": 0, "estimated_live_cost_cents": 1053, "cost_breakdown": { "handling_cents": 250, "printing_cents": 120, "printing_per_page_cents": 40, "postage_cents": 608, "postage_description": "Certified (postage + $5.30 fee)", "color_surcharge_cents": 75, "page_count": 3, "weight_oz": 0.89 }, "test_mode": true, "metadata": { "campaign": "q2-notices" } }, "message": "Test outbound mail created and mail.submitted webhook queued. Advance the lifecycle via POST /api/v1/test/mail/:id/advance" } The webhook payload also includes estimated_live_cost_cents and cost_breakdown so developers can validate cost handling end-to-end. ### POST /v1/test/mail/:id/advance — Advance outbound mail status Steps a test_mode record through the full outbound lifecycle and fires the appropriate webhook at each step. Works on any test_mode=true record (created via POST /v1/test/mail or POST /v1/mail with an sk_agent_test_ key). Each step simulates the full facility fulfillment workflow: submitted → ready: - Sets claimed_at - Adds fulfillment photos: pages (printed document) + envelope (sealed with label) - Webhook includes fulfillment_photos array with photo URLs ready → mailed: - Sets mailed_at, dispatch_method (post_office) - Assigns carrier-format tracking number: USPS first_class/priority: TEST9400111899223XXXXXX USPS certified: TEST7019110000XXXXXXXXX FedEx: TEST7489XXXXXXXX UPS: TEST1Z999AA1XXXXXXXX - Sets carrier (USPS/FedEx/UPS) based on mail_class - Adds receipt photo to fulfillment_photos - Webhook includes tracking_number, carrier, dispatch_method, all photos mailed → delivered: - Sets delivered_at - Webhook includes all prior data POST /v1/test/mail/550e8400-.../advance Authorization: Bearer sk_agent_test_... Response (200): { "outbound_mail": { "id": "550e8400-...", "previous_status": "submitted", "status": "ready", "tracking_number": null, "carrier": null, "dispatch_method": null, "claimed_at": "2026-04-29T...", "mailed_at": null, "delivered_at": null, "fulfillment_photos": 2, "test_mode": true }, "message": "Advanced 'submitted' → 'ready' and webhook queued." } Call again to continue through each step. The dashboard progress tracker displays fulfillment photos, tracking info, and dispatch details at each stage. Errors: 403 if record is not test_mode; 409 if already at terminal status. ### POST /v1/test/webhook — Fire a sample webhook event Sends a realistic sample payload to your configured webhook endpoint. Use this to verify your handler is receiving, validating signatures, and responding correctly before going live. POST /v1/test/webhook Authorization: Bearer sk_agent_test_... Content-Type: application/json { "event_type": "mail.submitted" } Supported event_type values: mail.pending_approval, mail.submitted, mail.ready, mail.mailed, mail.delivered, mail.failed, mail.cancelled, package.received, action.created, action.completed Response (200): { "queued": true, "event_type": "mail.submitted", "webhook_url": "https://your-server.com/webhook", "message": "Test mail.submitted event queued. Check your webhook endpoint for delivery.", "sample_payload": { ... } } Error: 422 if no webhook URL is configured — set one via PUT /v1/webhooks/settings first. ### POST /v1/test/packages — Create a fake inbound mailpiece Creates a real package record in the agent's active mailbox and fires a package.received webhook. Use this to exercise the full inbound flow — the package can then be acted on (open_and_scan, forward, etc.) via the standard packages API. POST /v1/test/packages Authorization: Bearer sk_agent_test_... Content-Type: application/json { "carrier": "USPS", "tracking_number": "TEST9400111899223756891", "weight_oz": 4.5 } All body fields are optional. Identify test packages by the [TEST] prefix in the notes field. Response (201): package object with test: true. --- ## Batch / Mass Mailing — Send Hundreds or Thousands of Mail Pieces Send up to 10,000 identical mail pieces (postcards, letters, flyers, pamphlets) with a single upload. Upload one template PDF + a CSV of recipient addresses. The facility prepares and mails every piece with proof photos. All rates ~50% off USPS retail — cheaper than Lob, PostGrid, and other mail APIs. ### Use cases - Dental/medical appointment reminders and recall notices - Property management: lease renewals, rent increases, maintenance updates - Law firm client letters, demand notices, compliance mailings - Nonprofit donor thank-yous, fundraising appeals, event invitations - Marketing campaigns: promotional postcards, seasonal mailers, new product announcements - Accounting: engagement letters, tax notice replies, invoice packets, payment-plan letters, check remittance cover letters, organizer packets, and year-end statements. See https://mailbox.bot/use-cases/accounting-mail and https://mailbox.bot/use-cases-accounting-mail.md - Permits and buildouts: data-center site development, land buildouts, utility hookup applications, zoning notices, environmental responses, noise notices, labor letters, supplier packets, easement requests, public-signature packets, and agency follow-ups. See https://mailbox.bot/use-cases/permits-buildouts and https://mailbox.bot/use-cases-permits-buildouts.md - Business invoices, statements, newsletters ### Mail types | Type | Description | |------|-------------| | postcard_4x6 | Standard postcard | | postcard_6x9 | Jumbo postcard | | letter | Letter (1-3 pages in envelope) | | flyer | Single-sheet flyer | | pamphlet_light | Pamphlet/booklet, 4-8 pages | | pamphlet_heavy | Pamphlet/booklet, 9-16 pages | ### Mailing classes | Class | Delivery | Minimum | |-------|----------|---------| | first_class | 1-3 business days | No minimum | | marketing_mail | 3-10 business days | 200 pieces (USPS requirement) | Marketing Mail is for advertising, solicitation, and promotional content. Transactional mail (invoices, statements, appointment reminders) should use First-Class. ### Pricing (per piece) | Type | First-Class | Marketing Mail | USPS Retail | |------|-------------|----------------|-------------| | Postcard 4×6 | $0.28 | $0.28 | $0.56 | | Postcard 6×9 | $0.28 | $0.28 | $0.56 | | Letter (1pg) | $0.39 | $0.35 | $0.78 | | Letter (2-3pg) | $0.54 | $0.35 | $1.07 | | Flyer | $0.39 | $0.55 | $0.78 | | Pamphlet 4-8pg | $0.68 | $0.68 | $1.36 | | Pamphlet 9-16pg | $0.83 | $0.68 | $1.65 | Color printing: +$0.15/piece. Volume discounts: 500+ 5% off, 1,000+ 10% off, 5,000+ 15% off. ### CSV format Required columns: name, address, city, state, zip. Max 10,000 rows per batch. Accepted aliases: full_name → name, street/address1 → address, zipcode/postal_code → zip, st → state. Example: name,address,city,state,zip Jane Smith,123 Main St,Los Angeles,CA,90210 John Doe,456 Oak Ave Apt 2B,New York,NY,10001 ### POST /dashboard/batch-mail — Create batch draft Content-Type: multipart/form-data (cookie auth, dashboard route) POST /api/dashboard/batch-mail Content-Type: multipart/form-data template: (PDF file, max 10MB) csv: (CSV file, max 5MB, 10,000 rows) job_name: Q2 Recall Notices mail_type: postcard_4x6 mailing_class: first_class color: false duplex: false Response (201): { "draft": { "id": "batch-uuid", "jobName": "Q2 Recall Notices", "mailType": "postcard_4x6", "mailingClass": "first_class", "totalPieces": 500, "csvValidCount": 500, "csvErrorCount": 3, "perPieceCents": 28, "volumeDiscountPct": 5, "subtotalCents": 14000, "discountCents": 700, "totalCents": 13300, "retailTotalCents": 28000, "status": "draft" }, "csvPreview": [ { "name": "Jane Smith", "line1": "123 Main St", "city": "LA", "state": "CA", "zip": "90210" } ] } ### POST /dashboard/batch-mail/:id/confirm — Confirm and pay Charges the member's saved card for the full batch total. Returns Stripe PaymentIntent ID. POST /api/dashboard/batch-mail/:id/confirm Response (200): { "status": "paid", "stripePaymentIntentId": "pi_xxx" } ### POST /dashboard/batch-mail/:id/cancel — Cancel batch Cancels the batch and refunds if already paid. Only cancellable before facility starts printing. ### GET /dashboard/batch-mail/estimate — Quick price estimate GET /api/dashboard/batch-mail/estimate?mail_type=postcard_4x6&mailing_class=first_class&quantity=500&color=false Returns pricing breakdown without uploading files. ### Batch status lifecycle draft → paid → generating → submitted → in_progress → completed ↘ partially_completed ↘ failed draft/paid/generating/submitted → cancelled (with refund) ### Webhook events | Event | When | |-------|------| | batch.submitted | All pieces generated, batch queued for facility | | batch.completed | Facility finished printing and mailing all pieces | | batch.failed | Batch processing failed | --- ## Agent Instructions (MAILBOX.md) — Operating Contract The MAILBOX.md is the operating contract between a renter and their agent. The renter configures standing instructions through the dashboard that define how the agent should handle mail — default handling, forward rules, auto-shred criteria, scanning preferences, outbound mail class, special instructions, and communication preferences. This is not optional context. Agents MUST fetch their MAILBOX.md before taking any action and follow the renter's rules as their default behavior. Human prompts from the renter override standing rules, but in the absence of direct instructions, the MAILBOX.md governs. ### How it works 1. **Agent boots** — fetches MAILBOX.md via `GET /v1/agents/:agent_id/instructions`, caches the version number 2. **Mail arrives** — agent receives webhook with MAILBOX.md included in payload, applies rules to decide what to do 3. **Agent acts** — every mutating request includes `X-Mailbox-MD-Version` header, platform rejects stale versions 4. **Agent reports** — summarizes actions taken and which MAILBOX.md rule drove each decision 5. **Renter updates instructions** — version increments, agent gets 409 on next action, re-fetches and adapts ### MAILBOX.md sections | Section | Purpose | |---------|---------| | Default Handling | What to do when no other rule matches (e.g. "Hold 7 days, photograph on arrival") | | Forward Rules | Which mail to forward and where (e.g. "Forward government mail to home address") | | Auto-Shred | What to destroy without asking (e.g. "Shred junk from known marketers") | | Scanning | What to open and scan (e.g. "Scan envelopes from government agencies") | | Outbound Mail | How to send mail (e.g. "Certified for legal, first class for business") | | Special Instructions | Hard rules (e.g. "Never dispose without explicit approval") | | Communication Preferences | How to notify the renter (e.g. "Email for all inbound, text for urgent") | ### GET /v1/agents/:agent_id/instructions — Get standing instructions Scope required: agent.read GET /v1/agents/:agent_id/instructions Authorization: Bearer sk_agent_... Agent-scoped keys auto-resolve to their own agent (path param ignored). Member keys must specify the target agent in the path. Response (200): { "mailbox_md": "# Default Handling\n\nHold all mail for 7 days, then notify me\nPhotograph exterior of every letter and envelope on arrival\n\n# Forward Rules\n\nForward mail from [sender] to: [address]\nForward all mail from government agencies immediately\n\n# Auto-Shred\n\nShred junk mail from known marketing senders\nShred anything addressed to \"Current Resident\"\n\n# Scanning\n\nOpen and scan documents from [company]\nScan all envelopes from government agencies\n\n# Outbound Mail\n\nUse certified mail for government agencies and legal correspondence\nUse first class for standard business letters\nNever send mail outside the US without explicit approval\nAlways include my suite number as the return address\n\n# Special Instructions\n\nNever dispose of anything without explicit approval\nText me for anything marked \"urgent\"\n\n# Communication Preferences\n\nNotify me by email for all inbound mail\nNotify me when document scans are complete\nNotify me of outbound mail status changes\nContact me for clarification before disposing of anything\nPreferred channel: email", "version": 3, "hash": "a1b2c3d4e5f6...", "updated_at": "2026-03-15T10:30:00Z" } Fields: - mailbox_md — full instruction content (Markdown) - version — monotonic counter, increments on every edit by the renter - hash — SHA-256 hex digest of the content (for cache invalidation) - updated_at — when the renter last edited the instructions ### Webhook payload enrichment Every webhook payload is automatically enriched with the agent's current MAILBOX.md: { "event_type": "package.received", "package_id": "pkg-uuid", "carrier": "fedex", "tracking_number": "794644790132", "weight_oz": 12.4, "mailbox_md": "# Default Handling\nHold all mail for 7 days...", "mailbox_md_version": 3, "mailbox_md_hash": "a1b2c3d4..." } This means the agent always has current instructions in context when processing events — no separate fetch needed during webhook handling. For OpenClaw agents using `payload_format: "openclaw"`, the MAILBOX.md appears as a "Standing instructions (vN)" section appended to the message, giving the agent full context in every webhook delivery. ### Version enforcement Mutating endpoints require an `X-Mailbox-MD-Version` header matching the current version. This prevents agents from acting on stale instructions. Enforced on (agent-scoped keys): - POST /v1/packages/:id/actions — request action (version stored in action_requests row) - POST /v1/packages/:id/forward — request forwarding - POST /v1/mail — send outbound mail (version stored in outbound_mail row) - PATCH /v1/packages/:id/actions/:actionId — update action (version stored in action_requests row) POST /v1/packages/:id/actions Authorization: Bearer sk_agent_... X-Mailbox-MD-Version: 3 Content-Type: application/json {"action": "scan"} If the version is missing, returns 400: { "error": "MAILBOX_MD_VERSION_REQUIRED", "message": "X-Mailbox-MD-Version header is required..." } If the version is stale, returns 409: { "error": "MAILBOX_MD_VERSION_MISMATCH", "message": "Your agent is using MAILBOX.md v2 but the current version is v3...", "your_version": 2, "current_version": 3, "updated_at": "2026-03-15T10:30:00Z" } ### Recommended integration pattern 1. On boot: GET /v1/agents/:id/instructions — cache version and content 2. On webhook: read mailbox_md from payload — no extra fetch needed 3. On action: include X-Mailbox-MD-Version header 4. On 409: re-fetch instructions, update cache, retry action 5. Periodically: re-fetch instructions to catch changes between webhooks 6. On every action: add a note explaining which MAILBOX.md rule drove the decision --- ## Webhook Event Inspection Inspect webhook delivery history to debug failed deliveries, verify payloads, and monitor delivery health. Dashboard: Log in at https://mailbox.bot/dashboard/webhooks to see all webhook events (sandbox and live) in a visual log with status filters, payload inspection, and per-attempt delivery details. No curl required — every event fired by test keys or live keys appears here. ### GET /v1/webhooks/events — List webhook delivery events Scope required: webhook.read GET /v1/webhooks/events?agent_id=...&status=failed&limit=20 Agent-scoped keys auto-filter to their own events. Member keys should pass agent_id to scope results. Facility keys cannot access this endpoint. Query parameters: - agent_id: filter by agent (required for member keys, automatic for agent keys) - status: pending | delivered | failed (optional) - event_type: filter by event type, e.g. package.received (optional) - limit: 1-100, default 20 - offset: pagination offset Response (200): { "events": [ { "id": "event-uuid", "agent_id": "agent-uuid", "package_id": "pkg-uuid", "mailbox_id": "mb-uuid", "event_type": "package.received", "status": "delivered", "created_at": "2026-03-15T10:30:00Z", "delivered_at": "2026-03-15T10:30:01Z" } ], "pagination": { "total": 47, "limit": 20, "offset": 0, "has_more": true } } ### GET /v1/webhooks/events/:event_id — Get event with delivery attempts Scope required: webhook.read GET /v1/webhooks/events/:event_id Returns the full event payload and every delivery attempt with HTTP status codes, response times, and error messages. Response (200): { "event": { "id": "event-uuid", "agent_id": "agent-uuid", "package_id": "pkg-uuid", "mailbox_id": "mb-uuid", "event_type": "package.received", "payload": { ... }, "status": "failed", "created_at": "2026-03-15T10:30:00Z", "delivered_at": null }, "delivery_attempts": [ { "attempt_number": 1, "http_status_code": 500, "response_time_ms": 2340, "success": false, "error_message": "Server returned 500", "attempted_at": "2026-03-15T10:30:01Z" }, { "attempt_number": 2, "http_status_code": 200, "response_time_ms": 145, "success": true, "error_message": null, "attempted_at": "2026-03-15T10:35:01Z" } ] } --- ## Usage & Billing ### GET /v1/usage — Get usage summary and events Scope required: billing.read GET /v1/usage?agent_id=...&event_type=package.forwarded&period_start=2026-02-01&limit=50 Query parameters: - agent_id: filter by agent (optional, automatic for agent-scoped keys) - event_type: filter by event type (optional) - billed: true | false (optional) - period_start: ISO 8601 datetime (default: first day of current month) - period_end: ISO 8601 datetime (default: last day of current month) - limit: 1-200, default 50 - offset: pagination offset Response (200): { "period": { "start": "2026-02-01T00:00:00Z", "end": "2026-02-28T23:59:59Z" }, "summary": { "total_events": 47, "total_cents": 15800, "billed_cents": 12000, "unbilled_cents": 3800, "by_type": { "package.forwarded": { "count": 12, "total_cents": 10800 }, "package.scan": { "count": 15, "total_cents": 4500 }, "package.photograph": { "count": 20, "total_cents": 500 } } }, "events": [ ... ], "pagination": { "total": 47, "limit": 50, "offset": 0, "has_more": false } } --- ## Discovery & Protocols All discovery endpoints are public — no authentication required. ### GET /api/mcp/tools-public — MCP Tool Catalog Returns all 29 available MCP tools with input schemas for the Model Context Protocol (spec 2025-11-25). Important inbound/outbound drafting tools: - `list_inbound_forwarding_addresses` - `list_inbound_mail` - `get_inbound_mail` - `list_postal_threads` - `get_postal_thread` - `send_outbound_mail` — also accepts `inbound_capture_id` and `postal_mail_thread_id` The full tool catalog also includes mailbox, package, scan, rule, usage, webhook, messaging, outbound mail, and sandbox lifecycle tools. Transport: streamable-http at /api/mcp ### GET /api/a2a/agent-card — A2A Agent Card Returns the Agent-to-Agent Protocol (v0.3) agent card with 9 skills: - provision-address, receive-packages, package-forwarding, document-scanning - package-actions, standing-instructions, expected-shipment-matching - agent-memory, webhook-notifications Also available at: - /.well-known/agent-card.json (standard A2A well-known path) - /.well-known/a2a-agent-card (legacy alias) ### GET /.well-known/agent.json — OpenClaw Agent Card Multi-protocol discovery document with endpoints, capabilities, auth schemes, onboarding info, and pricing. ### GET /security.md — Security Overview Key scoping model, spend and approval controls, webhook signing, idempotency, rate limiting, and vulnerability disclosure contact. Machine-readable contact also at /.well-known/security.txt. --- ## Quick Reference POST /v1/signup Create account (public, no auth) POST /v1/waitlist Reserve address spot (public, no auth) POST /v1/api-keys Create member API key GET /v1/api-keys List member API keys GET /v1/api-keys/:id Get key details DELETE /v1/api-keys/:id Revoke key POST /v1/api-keys/:id/rotate Rotate key POST /v1/agents Create agent/protocol endpoint; address issuance requires reservation/approval GET /v1/agents List agents GET /v1/agents/:id Get agent details PATCH /v1/agents/:id Update agent (incl. mailbox_md) DELETE /v1/agents/:id Deactivate agent POST /v1/agents/:id/credentials Create agent credential GET /v1/agents/:id/credentials List agent credentials GET /v1/agents/:id/credentials/:cid Get credential details DELETE /v1/agents/:id/credentials/:cid Revoke credential POST /v1/mailboxes Provision address (restricted beta only) GET /v1/mailboxes List addresses GET /v1/packages List packages GET /v1/packages/:id Get package details GET /v1/packages/:id/photos List package photos POST /v1/packages/:id/actions Request action GET /v1/packages/:id/actions List actions POST /v1/packages/:id/forward Request forwarding GET /v1/packages/:id/forward Forwarding status POST /v1/packages/:id/scan Request scan GET /v1/packages/:id/scan Get scan results POST /v1/packages/:id/tags Add tag GET /v1/packages/:id/tags List tags DELETE /v1/packages/:id/tags Remove tag POST /v1/packages/:id/notes Add note GET /v1/packages/:id/notes List notes POST /v1/agents/:id/expected-shipments Register expected shipment GET /v1/agents/:id/expected-shipments List expected shipments DELETE /v1/agents/:id/expected-shipments Cancel expected shipment POST /v1/agents/:id/rules Create standing instruction GET /v1/agents/:id/rules List rules PATCH /v1/agents/:id/rules Update rule DELETE /v1/agents/:id/rules Delete rule POST /v1/messages Send support message GET /v1/messages List conversations GET /v1/messages/:id Get full thread GET /v1/agents/:id/instructions Get standing instructions (MAILBOX.md) GET /v1/webhooks/settings Get webhook config PUT /v1/webhooks/settings Update webhook config POST /v1/webhooks/signing-keys/rotate Rotate webhook signing key GET /v1/webhooks/events List webhook delivery events GET /v1/webhooks/events/:id Get event + delivery attempts GET /v1/usage Usage summary + events GET /api/mcp/tools-public MCP tool catalog (public) GET /api/a2a/agent-card A2A agent card (public) GET /.well-known/agent-card.json A2A agent card, standard path (public) GET /.well-known/agent.json OpenClaw agent card (public) GET /openapi.json OpenAPI 3.1 spec, JSON (public) GET /openapi.yaml OpenAPI 3.1 spec, YAML (public) --- ## Public repos - MCP server: https://github.com/arbengine/mailbox-mcp - MCP skill: https://github.com/arbengine/mailbox-bot-skill --- mailbox.bot — https://mailbox.bot API docs (interactive): https://mailbox.bot/api-docs Contact: https://mailbox.bot/contact