# mailbox.bot — Full API Reference > Software infrastructure for managed micro-fulfillment. Connects people, businesses, and AI agents with independently licensed facility operators. Dashboard or API. > Base URL: https://api.mailbox.bot > Summary: https://mailbox.bot/llms.txt --- ## Authentication All authenticated requests require a Bearer token in the Authorization header. Authorization: Bearer sk_live_xxxxxxxxxxxxx Three 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 - 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/staging ### 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. This is the key type you hand to your AI agent. **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 addresses 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 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/staging", "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. Complete Stripe Identity KYC at https://mailbox.bot/staging (~2 minutes) 3. Accept Terms of Service 4. Select a plan and add payment 5. 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. ### POST /v1/waitlist — Join waitlist (no account created) Rate limit: 5 requests per minute per IP. No authentication required. For agents not ready to onboard — just register interest. POST /v1/waitlist Content-Type: application/json { "email": "agent@yourco.com", "needs": "sensor kits, need SoCal address, ~20 pkgs/month" } Response (200): { "success": true } --- ## 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" } ### 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 Rate limit: 100 requests/minute per API key (standard). Custom limits for enterprise. --- ## Webhook Security Every webhook includes an X-Mailbox-Signature header containing an HMAC-SHA256 signature of the request body, computed using your webhook signing key. Signing keys are returned when you create an agent (POST /v1/agents response). Manage keys via GET/PUT /v1/webhooks/settings. --- ## 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, subdomain endpoint, and can own multiple physical addresses (mailboxes). 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": { "id": "MB-7F3A2K9P", "reference_code": "7F3A", "facility_id": "...", "facility_address": { "line1": "1234 Logistics Pkwy", "line2": "Ref 7F3A", "city": "City", "state": "ST", "zip": "00000" } }, "webhook_signing_key": "whsk_xxxxxxxxxxxxx", "created_at": "2026-02-09T14:23:10Z" } Note: Creating an agent automatically provisions a first mailbox at the default active facility and generates a webhook signing key. ### 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" } Updatable fields: display_name, description, webhook_url, capabilities, public, status ### 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 } Required fields: scopes (array, at least one valid scope) Optional fields: expires_in_days (integer) Maximum 5 active credentials per agent. Agent must be active. Valid scopes: mailbox.read, package.read, package.write, forward.create, scan.create, billing.read, webhook.manage, 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" }, "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. Each endpoint gets a unique reference code at a facility. ### POST /v1/mailboxes — Provision a physical endpoint Scope required: mailbox.write 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 - 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. --- ## 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 --- ## 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) Rate limited. POST /v1/packages/:package_id/actions 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. POST /v1/packages/:package_id/forward 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: package.write 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 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 --- ## Outbound Mail — Send Physical Mail via API Agents can submit pre-rendered PDFs for physical printing and mailing. The facility prints exactly what you upload (WYSIWYG) and mails it to the recipient address. ### POST /v1/mail — Submit outbound mail Scope required: mail.send (agent-scoped key required) Requires: X-Mailbox-MD-Version header Supports: Idempotency-Key header for safe retries Content-Type: multipart/form-data POST /v1/mail Authorization: Bearer sk_agent_... X-Mailbox-MD-Version: 3 Content-Type: multipart/form-data document: (PDF file, max 10MB) 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 | PDF file (max 10MB, validated for structure + page count) | | 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 override | | return_line1 | No | Return address line 1 override | | return_line2 | No | Return address line 2 | | return_city | No | Return address city | | return_state | No | Return address state (2-letter) | | return_zip | No | Return address ZIP | | mail_class | No | first_class (default), priority, certified, certified_return_receipt | | 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 | 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": 800, "cost_display": "$8.00", "created_at": "2026-03-15T10:30:00Z", "pdf_url": "https://..." } } Error responses: - 400 Bad Request — missing required fields, invalid state/zip, PDF validation failure - 404 Not Found — no active mailbox for this agent - 409 Conflict — MAILBOX_MD_VERSION_MISMATCH (re-fetch instructions and retry) ### GET /v1/mail — List outbound mail GET /v1/mail?status=mailed&limit=20&offset=0 Filters: status (submitted|printing|mailed|delivered|failed|cancelled), limit (max 100), offset ### GET /v1/mail/:id — Get outbound mail details Returns all fields including a freshly-generated signed PDF URL (1-hour expiry). ### 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 submitted → printing → mailed → delivered ↘ failed submitted → cancelled ### Webhook events Each status transition fires a webhook to the agent: | Event | When | |-------|------| | mail.submitted | Agent uploads PDF, job queued | | mail.printing | Facility starts printing | | 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, mail_class, status, tracking_number, carrier, cost_cents, pdf_url, callback_url, and the agent's MAILBOX.md + version. ### Pricing | Mail class | Base price | |------------|-----------| | First Class | $1.50 | | Priority | $3.50 | | Certified | $6.00 | | Certified + Return Receipt | $8.00 | | Color printing | +$0.25/page | | Duplex printing | +$0.50 flat | Prices may vary by plan. Use GET /api/dashboard/pricing for live rates. --- ## 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: - POST /v1/packages/:id/actions — request action - POST /v1/packages/:id/forward — request forwarding - POST /v1/mail — send outbound mail - PATCH /v1/packages/:id/actions/:actionId — update action 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. ### 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 — MCP Tool Catalog Returns all 15 available MCP tools with input schemas for the Model Context Protocol (spec 2025-11-25). Tools: get_mailbox, list_packages, get_package, get_package_photos, request_action, request_scan, get_scan_results, add_tag, add_note, create_rule, register_expected, get_usage, update_webhook, and more. 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/a2a-agent-card ### GET /.well-known/agent.json — OpenClaw Agent Card Multi-protocol discovery document with endpoints, capabilities, auth schemes, onboarding info, and pricing. --- ## Quick Reference POST /v1/signup Create account (public, no auth) POST /v1/waitlist Join waitlist (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 + first address GET /v1/agents List agents GET /v1/agents/:id Get agent details PATCH /v1/agents/:id Update agent 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 additional address 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 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 MCP tool catalog (public) GET /api/a2a/agent-card A2A agent card (public) GET /.well-known/agent.json OpenClaw agent card (public) --- mailbox.bot — https://mailbox.bot API docs (interactive): https://mailbox.bot/api-docs Contact: founders@mailbox.bot