---
openapi: 3.1.0
info:
  title: mailbox.bot API
  version: 1.0.0
  description: 'The physical postal mail API for AI agents and software workflows.
    Send letters, certified postal mail, postcards, notices, and documents from code.
    For inbound context, forward scans, photos, PDFs, virtual mailbox notices, and
    notes from addresses you already use to a private mailbox.bot alias; mailbox.bot
    stores the evidence, runs OCR/extraction, and links it to outbound replies. Managed
    physical receiving and package forwarding are separate account-enabled services.
    MCP, A2A, and OpenClaw protocols supported. Webhook auth modes: hmac (HMAC-SHA256,
    default), bearer (Authorization header), header (custom header name).'
  contact:
    url: https://mailbox.bot/contact
  termsOfService: https://mailbox.bot/terms
servers:
- url: https://mailbox.bot/api
  description: Production
security:
- BearerAuth: []
components:
  securitySchemes:
    BearerAuth:
      type: http
      scheme: bearer
      description: |-
        API key from your mailbox.bot dashboard. Four key types:

        - **sk_live_** (Member keys) — Full account access with all scopes. Queries span all agents unless filtered with ?agent_id=.
        - **sk_agent_** (Agent keys) — Scoped to a single agent's resources. Queries auto-filter to that agent; the agent_id parameter is ignored.
        - **sk_agent_test_** (Test keys) — Same as sk_agent_ but activates sandbox mode: full PDF validation, real cost calculation, HMAC-signed webhooks — but no Stripe charge and no facility fulfillment. All responses include `X-Test-Mode: true` header and `test_mode: true` in the body. Create via POST /v1/agents/:id/credentials with environment: 'test'.
        - **sk_facility_** (Facility keys) — Scoped to a facility's resources for external scanner apps.

        Member keys require ?agent_id= on agent-specific endpoints (rules, expected-shipments, instructions) to specify which agent to target. Agent keys resolve this automatically.

        **Sandbox mode:** Use a test key (sk_agent_test_) with the same production endpoints. Responses include test_mode: true, cost_cents: 0, estimated_live_cost_cents (what production would charge), and a cost_breakdown object. Your code stays identical — just swap the API key to go live.
  schemas:
    Error:
      type: object
      required:
      - error
      properties:
        error:
          type: string
          description: Human-readable error message
        fields:
          type: array
          description: Structured validation issues. Present on 400 validation failures.
          items:
            type: object
            required:
            - path
            - message
            properties:
              path:
                type: string
                description: Dot-path to the invalid field
              message:
                type: string
                description: Validation message for that field
        reason:
          type: string
          description: 'Machine-readable reason code (present on 401/403 responses).
            401 codes: token_missing, token_malformed, token_prefix_unknown, key_not_found,
            key_revoked, key_expired, member_suspended, facility_inactive. 403 codes:
            insufficient_scope.'
        retryable:
          type: boolean
          description: Whether the request can be retried. false for validation/payment
            errors, true for transient failures (5xx) and idempotency conflicts (409).
            Present on outbound mail errors.
        suggested_action:
          type: string
          description: Plain-language guidance for recovering from the error. Designed
            for AI agents to read and act on. Present on outbound mail errors.
    SignupRequest:
      type: object
      required:
      - email
      - password
      - full_name
      properties:
        email:
          type: string
          format: email
        password:
          type: string
          minLength: 8
        full_name:
          type: string
          minLength: 2
          maxLength: 100
        needs:
          type: string
          description: Optional description of what you need
        lead_id:
          type: string
          pattern: "^wl_[a-f0-9]{16}$"
          description: Optional. If your agent previously called POST /v1/waitlist
            and received a lead_id, pass it here. The new account will be stitched
            to that lead row, and the response will include a status_url you can poll
            without auth (via GET /v1/leads/{leadId}) to track funnel progress.
    LeadStatus:
      type: object
      required:
      - lead_id
      - stage
      - has_account
      - created_at
      properties:
        lead_id:
          type: string
        source:
          type: string
          description: How the lead arrived (web, api, agent)
        stage:
          type: string
          enum:
          - waitlisted
          - account_created
          - email_verified
          - active
          - unknown
          description: Funnel stage. Progresses left-to-right.
        has_account:
          type: boolean
          description: True once the lead has been stitched to a member account via
            /v1/signup.
        created_at:
          type: string
          format: date-time
        age_days:
          type: integer
        next_step:
          type: object
          nullable: true
          properties:
            action:
              type: string
            url:
              type: string
              format: uri
            hint:
              type: string
        agent_resources:
          type: object
          description: Directory of agent-discoverable URLs (OpenAPI, MCP, A2A, etc.)
        contact:
          type: object
          properties:
            owner_email:
              type: string
              format: email
            instruction_for_agent:
              type: string
    WaitlistRequest:
      type: object
      required:
      - email
      properties:
        email:
          type: string
          format: email
        needs:
          type: string
          description: Free-text description of what you (or your operator) need.
            Agents are encouraged to write a one-sentence summary of intent. Use this
            to reserve a limited spot for a new real mailing and package address
            with street address + mailbox number, scan/photo intake, package receiving,
            package forwarding, or agent-managed physical mail decisions. Address
            issuing begins August 31, 2026.
        preferred_location:
          type: string
          description: Optional. Desired region for address reservation, managed
            receiving, or package workflows, for example Southern California,
            Nevada, Southern Utah, or another requested market.
        callback_url:
          type: string
          format: uri
          description: Optional. An https webhook the agent will accept callbacks
            on. If supplied, mailbox.bot can reach the agent later (launch announcements,
            partnership inquiries, lead follow-up).
        agent_id:
          type: string
          description: Optional. A self-identifier for the agent platform/instance
            making the request. Helps mailbox.bot recognize repeat visits and route
            follow-ups.
        operator_contact:
          type: string
          description: Optional. A human-readable contact (email, handle, name) that
            the agent's operator authorized for follow-up. Only supply this if you
            have explicit operator consent.
    PackageSummary:
      type: object
      properties:
        id:
          type: string
          format: uuid
        mailbox_id:
          type: string
        agent_id:
          type: string
          format: uuid
        tracking_number:
          type: string
          nullable: true
        carrier:
          type: string
          nullable: true
        weight_oz:
          type: number
          nullable: true
        status:
          type: string
          enum:
          - received
          - stored
          - held
          - forwarding_requested
          - forwarding_shipped
          - forwarded
          - returning
          - shredded
          - disposed
          - returned
          - expired
        received_at:
          type: string
          format: date-time
        photos_count:
          type: integer
    ActionRequest:
      type: object
      required:
      - action
      properties:
        action:
          type: string
          enum:
          - scan
          - open_and_scan
          - photograph
          - record_video
          - forward
          - shred
          - dispose
          - return_to_sender
          - hold
          description: The action to perform on the package
        priority:
          type: string
          enum:
          - low
          - normal
          - high
          - urgent
          default: normal
        parameters:
          type: object
          description: Action-specific parameters (e.g. forwarding address for forward
            action)
    CreateAgentRequest:
      type: object
      required:
      - name
      properties:
        name:
          type: string
          minLength: 2
          maxLength: 50
        display_name:
          type: string
        description:
          type: string
        webhook_url:
          type: string
          format: uri
    AgentCredentialRequest:
      type: object
      required:
      - scopes
      properties:
        scopes:
          type: array
          items:
            type: string
            enum:
            - mailbox.read
            - mailbox.write
            - package.read
            - package.write
            - forward.create
            - scan.create
            - mail.send
            - inbound.read
            - billing.read
            - webhook.manage
            - webhook.read
            - agent.read
            - agent.write
            - message.send
          minItems: 1
          description: Scopes to grant the agent key. At least one required.
        expires_in_days:
          type: integer
          minimum: 1
          description: Optional key lifetime in days. Omit for no expiration.
        environment:
          type: string
          enum:
          - live
          - test
          default: live
          description: 'Key environment. ''test'' creates an sk_agent_test_ key that
            activates sandbox mode on all endpoints: full validation, real cost calculation,
            HMAC-signed webhooks — but no Stripe charge and no facility fulfillment.
            Default: ''live''.'
        force_approval:
          type: boolean
          default: false
          description: When true, all outbound mail submitted with this key is routed
            to pending_approval regardless of the requires_approval parameter. Use
            for agent governance — ensures a human reviews every mailpiece before
            it's sent.
        max_daily_pieces:
          type: integer
          minimum: 1
          nullable: true
          description: Maximum outbound mail pieces this agent can submit per 24-hour
            window. Null or omitted means no limit. Protects against runaway agent
            loops.
    AgentCredential:
      type: object
      properties:
        id:
          type: string
          format: uuid
        key:
          type: string
          description: Full raw key — returned only on creation, never shown again
        key_prefix:
          type: string
          description: First 18 characters of the key (always visible)
        credential_type:
          type: string
          enum:
          - api_key
        scopes:
          type: array
          items:
            type: string
        status:
          type: string
          enum:
          - active
          - revoked
        environment:
          type: string
          enum:
          - live
          - test
          description: Key environment — 'test' keys activate sandbox mode
        expires_at:
          type: string
          format: date-time
          nullable: true
        created_at:
          type: string
          format: date-time
        force_approval:
          type: boolean
          description: When true, all mail submitted with this key requires human
            approval
        max_daily_pieces:
          type: integer
          nullable: true
          description: Maximum mail pieces per 24h window (null = unlimited)
    MemberApiKeyRequest:
      type: object
      required:
      - name
      properties:
        name:
          type: string
          minLength: 1
          maxLength: 64
          description: Human-readable key name shown in the dashboard.
        environment:
          type: string
          enum:
          - prod
          - test
          default: prod
          description: Member key environment. Production keys are still prefixed
            sk_live_.
        expires_in_days:
          type: integer
          minimum: 1
          description: Optional key lifetime in days. Omit for no expiration.
    MemberApiKey:
      type: object
      properties:
        id:
          type: string
          format: uuid
        key:
          type: string
          description: Full raw key — returned only on creation or rotation, never
            shown again
        key_prefix:
          type: string
        name:
          type: string
        environment:
          type: string
          enum:
          - prod
          - test
        status:
          type: string
          enum:
          - active
          - revoked
          - rotated
        expires_at:
          type: string
          format: date-time
          nullable: true
        created_at:
          type: string
          format: date-time
    InboundMailFile:
      type: object
      properties:
        id:
          type: string
          format: uuid
        kind:
          type: string
          enum:
          - raw_email
          - attachment
        filename:
          type: string
        content_type:
          type: string
        size_bytes:
          type: integer
        sha256:
          type: string
          nullable: true
        extraction_status:
          type: string
          enum:
          - pending
          - extracted
          - pending_ocr
          - unsupported
          - failed
        download_url:
          type: string
          description: Present when include=files or signed_urls
        signed_url:
          type: string
          description: Present only when signed_urls=true or include=signed_urls
        created_at:
          type: string
          format: date-time
    InboundExtraction:
      type: object
      properties:
        id:
          type: string
          format: uuid
        inbound_mail_file_id:
          type: string
          format: uuid
          nullable: true
        source:
          type: string
          description: email_subject, email_body, pdf_metadata, ocr_placeholder, heuristic_classifier,
            address_parser, thread_matcher, etc.
        field:
          type: string
        value:
          type: string
        confidence:
          type: number
          minimum: 0
          maximum: 1
        metadata:
          type: object
        created_at:
          type: string
          format: date-time
    InboundReplyContact:
      type: object
      properties:
        id:
          type: string
        name:
          type: string
          nullable: true
        line1:
          type: string
        line2:
          type: string
          nullable: true
        city:
          type: string
        state:
          type: string
        postal_code:
          type: string
        country:
          type: string
        source_type:
          type: string
        verification_status:
          type: string
          nullable: true
        confidence:
          type: number
          nullable: true
        formatted_address:
          type: string
    InboundDraftContext:
      type: object
      description: Compact drafting context for LLMs and external agents. Present
        when include=drafting.
      properties:
        summary:
          type: string
          nullable: true
        category:
          type: string
        suggested_action:
          type: string
        inbound_capture_id:
          type: string
          format: uuid
        postal_mail_thread_id:
          type: string
          format: uuid
          nullable: true
        sender:
          type: object
        recipient:
          type: object
        reply_contact:
          oneOf:
          - "$ref": "#/components/schemas/InboundReplyContact"
          - type: 'null'
        reply_contact_confidence:
          type: number
          nullable: true
        deadlines:
          type: array
          items:
            type: string
        key_facts:
          type: array
          items:
            type: object
        source_files:
          type: array
          items:
            type: object
        linked_outbound_mail:
          type: array
          items:
            type: object
          description: Recent outbound mail records already linked to this inbound
            capture or thread.
    InboundForwardingAddress:
      type: object
      description: Private forwarding alias used to send scans, PDFs, photos, provider
        notices, notes, and other context-aware documents into mailbox.bot inbound
        context. This is a member-scoped intake email alias, not a new physical mailing
        address.
      properties:
        id:
          type: string
          format: uuid
        member_id:
          type: string
          format: uuid
        agent_id:
          type: string
          format: uuid
          nullable: true
        label:
          type: string
        alias_local_part:
          type: string
        domain:
          type: string
        email:
          type: string
        alias_type:
          type: string
          enum:
          - random
          - vanity
        requested_alias_local_part:
          type: string
          nullable: true
        source_type:
          type: string
        provider:
          type: string
          nullable: true
        handled_by:
          type: string
        capture_method:
          type: string
        automation_level:
          type: string
          enum:
          - high
          - medium
          - low
        approval_mode:
          type: string
          enum:
          - always_require
          - require_for_sensitive
          - allow_rules
        status:
          type: string
          enum:
          - active
          - paused
          - archived
        last_received_at:
          type: string
          format: date-time
          nullable: true
        metadata:
          type: object
        created_at:
          type: string
          format: date-time
        updated_at:
          type: string
          format: date-time
    InboundMailItem:
      type: object
      properties:
        id:
          type: string
          format: uuid
        object:
          type: string
          enum:
          - inbound_mail_item
        status:
          type: string
        received_at:
          type: string
          format: date-time
        from:
          type: string
          nullable: true
        subject:
          type: string
        note_preview:
          type: string
          nullable: true
        attachment_count:
          type: integer
        category:
          type: string
        suggested_action:
          type: string
        parsed:
          type: object
        thread:
          type: object
          nullable: true
        files:
          type: array
          items:
            "$ref": "#/components/schemas/InboundMailFile"
        context:
          type: object
          description: Compact agent-safe context. Full OCR/lineage requires include=ocr,lineage.
        draft_context:
          "$ref": "#/components/schemas/InboundDraftContext"
          description: Present only when include=drafting.
        extractions:
          type: array
          items:
            "$ref": "#/components/schemas/InboundExtraction"
          description: Present only when include=lineage.
    PostalThread:
      type: object
      properties:
        id:
          type: string
          format: uuid
        object:
          type: string
          enum:
          - postal_mail_thread
        title:
          type: string
        category:
          type: string
        status:
          type: string
          enum:
          - open
          - waiting
          - closed
          - archived
        summary:
          type: string
          nullable: true
        last_event_at:
          type: string
          format: date-time
        events:
          type: array
          description: Present when include=events.
          items:
            type: object
    OutboundMailWebhookPayload:
      type: object
      description: Webhook payload for outbound mail lifecycle events. The canonical
        event name field is event_type (not 'event'). tracking_number and carrier
        are null until mail.mailed. error_message is only present on mail.failed.
        metadata echoes back whatever you passed at submission — not wrapped or renamed.
        Use webhooks for push updates, or poll GET /v1/mail/{id} for canonical status
        and fresh signed photo URLs. Webhook payloads include callback_url for the
        same lookup.
      properties:
        event_type:
          type: string
          enum:
          - mail.pending_approval
          - mail.submitted
          - mail.ready
          - mail.mailed
          - mail.delivered
          - mail.failed
          - mail.cancelled
        outbound_mail_id:
          type: string
          format: uuid
        agent_id:
          type: string
          format: uuid
        mailbox_id:
          type: string
        package_id:
          type: string
          format: uuid
          nullable: true
          description: Set if this mail is a reply to an inbound package
        recipient_name:
          type: string
        recipient_city:
          type: string
        recipient_state:
          type: string
        recipient_zip:
          type: string
        return_name:
          type: string
          nullable: true
          description: Return address name (defaults to member profile if not overridden)
        return_line1:
          type: string
          nullable: true
          description: Return address line 1
        return_line2:
          type: string
          nullable: true
          description: Return address line 2
        return_city:
          type: string
          nullable: true
          description: Return address city
        return_state:
          type: string
          nullable: true
          description: Return address state (2-letter)
        return_zip:
          type: string
          nullable: true
          description: Return address ZIP
        mail_class:
          type: string
        status:
          type: string
        tracking_number:
          type: string
          nullable: true
          description: Present from mail.mailed onwards
        carrier:
          type: string
          nullable: true
          description: Present from mail.mailed onwards (e.g. USPS, FedEx, UPS)
        cost_cents:
          type: integer
          nullable: true
        created_at:
          type: string
          format: date-time
        mailed_at:
          type: string
          format: date-time
          nullable: true
        error_message:
          type: string
          nullable: true
          description: Failure detail — present only on mail.failed events
        agent_notes:
          type: string
          nullable: true
        metadata:
          type: object
          nullable: true
          description: Developer-supplied key-value pairs echoed from submission
        pdf_url:
          type: string
          format: uri
          nullable: true
          description: Signed URL to the uploaded PDF when the stored document is
            a PDF (1-hour expiry). Null for non-PDF formats.
        fulfillment_photos:
          type: array
          description: Proof photos taken by the facility. Empty on mail.submitted,
            populated on mail.ready/mailed, and may include delivery proof on mail.delivered.
            URLs are signed (1-hour expiry); use callback_url for fresh signed URLs.
          items:
            type: object
            properties:
              category:
                type: string
                enum:
                - pages
                - envelope
                - receipt
                - delivery
                description: 'Photo type: pages (printed doc), envelope (sealed mail),
                  receipt (postage/hand-off), delivery (postal delivery confirmation)'
              url:
                type: string
                format: uri
                description: Signed URL (1-hour expiry)
              uploaded_at:
                type: string
                format: date-time
        callback_url:
          type: string
          format: uri
          description: GET this URL to fetch the full mail record with fresh signed
            URLs
        test_mode:
          type: boolean
          description: Always present. false for live requests, true for sandbox/test
            requests.
        estimated_live_cost_cents:
          type: integer
          description: What this would cost in production. Present only when test_mode
            is true.
        mailbox_md:
          type: string
          nullable: true
          description: Agent's current MAILBOX.md instructions (injected by platform)
        mailbox_md_version:
          type: integer
          description: MAILBOX.md version at delivery time
        mailbox_md_hash:
          type: string
          nullable: true
          description: SHA-256 hex digest of MAILBOX.md content
webhooks:
  outboundMailLifecycle:
    post:
      operationId: outboundMailWebhook
      summary: Outbound mail lifecycle webhook
      description: Push delivery for outbound mail lifecycle events. The payload echoes
        submission metadata and includes callback_url for canonical polling with fresh
        signed URLs.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              "$ref": "#/components/schemas/OutboundMailWebhookPayload"
      responses:
        '200':
          description: Receiver acknowledged the webhook
paths:
  "/v1/signup":
    post:
      operationId: createAccount
      summary: Create an account (agent-initiated signup)
      description: |-
        Public endpoint. No auth required. Creates an account for a human operator. A verification email is sent automatically. Rate limited to 5 requests/minute per IP.

        **Agent UX:** The 201 response includes `relay_message` (a paste-ready sentence the agent can drop into its chat with the human) and `human_action_required` (a structured checklist with `verify_email`, `verify_phone`, `add_payment`, `select_plan`). Use these so the agent's runtime — Claude Desktop, Cursor, OpenAI Agents SDK, a custom Slack bot, etc. — can show the human exactly what to do next without inventing the wording. Mailbox.bot does not send any direct outreach to the operator beyond the Supabase email-verification link; the agent is the messenger.
      security: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              "$ref": "#/components/schemas/SignupRequest"
      responses:
        '201':
          description: 'Account created. Response: success, lead_id (if stitched),
            message, next_steps (verify_email, complete_kyc URL, after_kyc), human_action_required
            (array of {step, summary, blocker} — verify_email, verify_phone, add_payment,
            select_plan), relay_message (paste-ready sentence for the agent to send
            to its human), status_url (for poll without auth if lead_id stitched),
            agent_resources (OpenAPI/MCP/A2A directory), contact.owner_email.'
        '400':
          description: Validation error
        '409':
          description: Email already registered
        '429':
          description: Rate limit exceeded
  "/v1/waitlist":
    post:
      operationId: joinWaitlist
      summary: Reserve an agent address spot
      description: 'Public endpoint. No auth required. Reserve a limited agent mailing/package
        address spot without creating
        an account. Designed to be agent-friendly: the response includes a correlation
        lead_id, a directory of agent_resources (OpenAPI, MCP, A2A, agent cards),
        and a return_channel hint describing how to supply optional callback_url,
        agent_id, operator_contact, and preferred_location fields on a follow-up call
        so mailbox.bot can reach you back. Use this endpoint to reserve a new real
        mailing and package address with street address + mailbox number, scan/photo
        intake, package receiving, package forwarding, agent-managed physical mail
        decisions, or a requested launch region. Address issuing begins August 31,
        2026.'
      security: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              "$ref": "#/components/schemas/WaitlistRequest"
      responses:
        '200':
          description: Address reservation recorded. Response includes lead_id,
            agent_resources, next.signup_url, return_channel hint, and contact.owner_email.
        '400':
          description: Validation error (missing email or invalid callback_url)
        '429':
          description: Rate limit exceeded
  "/v1/leads/{leadId}":
    get:
      operationId: getLeadStatus
      summary: Get lead funnel status
      description: Public endpoint. No bearer auth required — the lead_id itself is
        the capability (64 bits of entropy, hard to guess). Returns the funnel stage
        of a lead created via POST /v1/waitlist, with a next_step hint and agent_resources
        directory. Designed so an agent (or its operator) can resume from any session
        using only the lead_id, even if the agent process has no other memory of the
        original call. Returns no PII — leaked lead IDs only reveal funnel position.
      security: []
      parameters:
      - name: leadId
        in: path
        required: true
        schema:
          type: string
          pattern: "^wl_[a-f0-9]{16}$"
        description: The lead_id returned by POST /v1/waitlist.
      responses:
        '200':
          description: Lead found
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/LeadStatus"
        '400':
          description: Invalid lead_id format
        '404':
          description: Lead not found
        '429':
          description: Rate limit exceeded
  "/v1/packages":
    get:
      operationId: listPackages
      summary: List packages
      description: List packages for the authenticated member or agent, with optional
        filters.
      parameters:
      - name: agent_id
        in: query
        schema:
          type: string
          format: uuid
        description: Filter by agent. Required for member keys (sk_live_) to scope
          results to a specific agent. Agent keys (sk_agent_) auto-filter to their
          own agent and ignore this parameter.
      - name: mailbox_id
        in: query
        schema:
          type: string
        description: Filter by mailbox reference code
      - name: status
        in: query
        schema:
          type: string
          enum:
          - received
          - stored
          - held
          - forwarding_requested
          - forwarding_shipped
          - forwarded
          - returning
          - returned
          - shredded
          - disposed
          - expired
        description: Filter by package status
      - name: carrier
        in: query
        schema:
          type: string
        description: Filter by carrier name
      - name: since
        in: query
        schema:
          type: string
          format: date-time
        description: Return packages received at or after this timestamp
      - name: before
        in: query
        schema:
          type: string
          format: date-time
        description: Return packages received at or before this timestamp
      - name: limit
        in: query
        schema:
          type: integer
          default: 20
          maximum: 100
      - name: offset
        in: query
        schema:
          type: integer
          default: 0
      responses:
        '200':
          description: Package list with pagination
        '401':
          description: Unauthorized
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/Error"
  "/v1/packages/{id}":
    get:
      operationId: getPackage
      summary: Get package details
      description: Get full package details including photos, events, label data,
        and actions.
      parameters:
      - name: id
        in: path
        required: true
        schema:
          type: string
          format: uuid
      responses:
        '200':
          description: Package details with photos, events, labels, actions
        '401':
          description: Unauthorized
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/Error"
        '404':
          description: Package not found
  "/v1/packages/{id}/photos":
    get:
      operationId: listPackagePhotos
      summary: List package photos
      description: Returns signed photo URLs plus OCR metadata for a package.
      parameters:
      - name: id
        in: path
        required: true
        schema:
          type: string
          format: uuid
      - name: photo_type
        in: query
        schema:
          type: string
        description: Optional filter by photo type
      responses:
        '200':
          description: Package photos with fresh signed URLs
        '401':
          description: Unauthorized
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/Error"
        '404':
          description: Package not found
  "/v1/packages/{id}/actions":
    get:
      operationId: listPackageActions
      summary: List actions for a package
      description: Returns the action requests already attached to a package.
      parameters:
      - name: id
        in: path
        required: true
        schema:
          type: string
          format: uuid
      responses:
        '200':
          description: Package action list
        '401':
          description: Unauthorized
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/Error"
        '404':
          description: Package not found
    post:
      operationId: requestAction
      summary: Request an action on a package
      description: 'Request an action such as scan, forward, shred, photograph, etc.
        on a specific package. Agent-scoped keys must include X-Mailbox-MD-Version
        header. Per-action scopes: forward requires forward.create, all others require
        package.write.'
      parameters:
      - name: id
        in: path
        required: true
        schema:
          type: string
          format: uuid
      - name: X-Mailbox-MD-Version
        in: header
        required: false
        schema:
          type: integer
        description: Agent's current MAILBOX.md version. Required for agent-scoped
          keys (sk_agent_). Returns 400 if missing, 409 if stale.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              "$ref": "#/components/schemas/ActionRequest"
      responses:
        '201':
          description: Action request created
        '400':
          description: Validation error or MAILBOX_MD_VERSION_REQUIRED
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/Error"
        '401':
          description: Unauthorized
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/Error"
        '403':
          description: Missing required scope (forward.create for forward, package.write
            for others)
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/Error"
        '404':
          description: Package not found
        '409':
          description: MAILBOX_MD_VERSION_MISMATCH — re-fetch instructions and retry
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/Error"
  "/v1/packages/{id}/actions/{actionId}":
    get:
      operationId: getPackageAction
      summary: Get package action details
      description: Returns the full action request record for a single package action.
      parameters:
      - name: id
        in: path
        required: true
        schema:
          type: string
          format: uuid
      - name: actionId
        in: path
        required: true
        schema:
          type: string
          format: uuid
      responses:
        '200':
          description: Action request detail
        '401':
          description: Unauthorized
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/Error"
        '404':
          description: Action request not found
    patch:
      operationId: updatePackageAction
      summary: Push an agent update to a package action
      description: Allows an external agent to attach notes, structured data, or clarification
        responses to an existing action request.
      parameters:
      - name: id
        in: path
        required: true
        schema:
          type: string
          format: uuid
      - name: actionId
        in: path
        required: true
        schema:
          type: string
          format: uuid
      - name: X-Mailbox-MD-Version
        in: header
        required: false
        schema:
          type: integer
        description: Required for agent-scoped keys. Returns 400 if missing and 409
          if stale.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                agent_notes:
                  type: string
                agent_data:
                  type: object
                respond_to_clarification:
                  type: string
                decision_context:
                  type: object
                  properties:
                    mailbox_md_section:
                      type: string
                    reasoning:
                      type: string
      responses:
        '200':
          description: Action request updated
        '400':
          description: Invalid body or action is not in an active state
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/Error"
        '401':
          description: Unauthorized
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/Error"
        '404':
          description: Action request not found
        '409':
          description: Concurrent modification or stale MAILBOX.md version
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/Error"
  "/v1/packages/{id}/forward":
    get:
      operationId: listPackageForwardingRequests
      summary: Get forwarding status for a package
      description: Returns the forwarding requests already created for a package.
      parameters:
      - name: id
        in: path
        required: true
        schema:
          type: string
          format: uuid
      responses:
        '200':
          description: Forwarding request list
        '401':
          description: Unauthorized
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/Error"
        '404':
          description: Package not found
    post:
      operationId: requestPackageForwarding
      summary: Request forwarding for a package
      description: Creates a forwarding request and transitions the package into forwarding_requested
        status.
      parameters:
      - name: id
        in: path
        required: true
        schema:
          type: string
          format: uuid
      - name: X-Mailbox-MD-Version
        in: header
        required: false
        schema:
          type: integer
        description: Required for agent-scoped keys. Returns 400 if missing and 409
          if stale.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
              - to_name
              - to_line1
              - to_city
              - to_state
              - to_zip
              properties:
                to_name:
                  type: string
                to_line1:
                  type: string
                to_line2:
                  type: string
                to_city:
                  type: string
                to_state:
                  type: string
                to_zip:
                  type: string
                to_country:
                  type: string
                  default: US
                carrier_preference:
                  type: string
                service_level:
                  type: string
                  default: ground
                insurance:
                  type: boolean
                  default: false
                signature_required:
                  type: boolean
                  default: false
      responses:
        '201':
          description: Forwarding request created
        '400':
          description: Missing required fields or package cannot be forwarded
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/Error"
        '401':
          description: Unauthorized
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/Error"
        '403':
          description: Missing forward.create scope or package belongs to a different
            agent
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/Error"
        '404':
          description: Package not found
        '409':
          description: MAILBOX_MD_VERSION_MISMATCH — re-fetch instructions and retry
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/Error"
  "/v1/packages/{id}/scan":
    get:
      operationId: listPackageScans
      summary: Get scan results for a package
      description: Returns document scan jobs already created for the package.
      parameters:
      - name: id
        in: path
        required: true
        schema:
          type: string
          format: uuid
      responses:
        '200':
          description: Document scan list
        '401':
          description: Unauthorized
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/Error"
        '404':
          description: Package not found
    post:
      operationId: requestPackageScan
      summary: Request a content scan for a package
      description: Creates a document scan job and a corresponding action request
        when needed.
      parameters:
      - name: id
        in: path
        required: true
        schema:
          type: string
          format: uuid
      requestBody:
        required: false
        content:
          application/json:
            schema:
              type: object
              properties:
                scan_type:
                  type: string
                  enum:
                  - label
                  - envelope
                  - document
                  - content
                  default: document
                options:
                  type: object
      responses:
        '201':
          description: Scan request created
        '400':
          description: Invalid scan_type
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/Error"
        '401':
          description: Unauthorized
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/Error"
        '403':
          description: Missing scan.create scope or package belongs to a different
            agent
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/Error"
        '404':
          description: Package not found
  "/v1/packages/{id}/tags":
    get:
      operationId: listPackageTags
      summary: List package tags
      description: Returns the tags currently attached to a package.
      parameters:
      - name: id
        in: path
        required: true
        schema:
          type: string
          format: uuid
      responses:
        '200':
          description: Package tags
        '401':
          description: Unauthorized
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/Error"
        '404':
          description: Package not found
    post:
      operationId: addPackageTag
      summary: Add a tag to a package
      description: Accepts tag, label, or name in the request body.
      parameters:
      - name: id
        in: path
        required: true
        schema:
          type: string
          format: uuid
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                tag:
                  type: string
                label:
                  type: string
                name:
                  type: string
      responses:
        '201':
          description: Tag created
        '400':
          description: Invalid or missing tag
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/Error"
        '401':
          description: Unauthorized
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/Error"
        '404':
          description: Package not found
    delete:
      operationId: removePackageTag
      summary: Remove a tag from a package
      parameters:
      - name: id
        in: path
        required: true
        schema:
          type: string
          format: uuid
      - name: tag
        in: query
        required: true
        schema:
          type: string
      responses:
        '200':
          description: Tag removed
        '400':
          description: Missing tag query parameter
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/Error"
        '401':
          description: Unauthorized
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/Error"
        '404':
          description: Package or tag not found
  "/v1/packages/{id}/notes":
    get:
      operationId: listPackageNotes
      summary: List package notes
      description: Returns agent notes attached to a package.
      parameters:
      - name: id
        in: path
        required: true
        schema:
          type: string
          format: uuid
      responses:
        '200':
          description: Package notes
        '401':
          description: Unauthorized
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/Error"
        '404':
          description: Package not found
    post:
      operationId: addPackageNote
      summary: Add a note to a package
      description: Accepts note, text, or message in the request body.
      parameters:
      - name: id
        in: path
        required: true
        schema:
          type: string
          format: uuid
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                note:
                  type: string
                text:
                  type: string
                message:
                  type: string
                metadata:
                  type: object
      responses:
        '201':
          description: Note created
        '400':
          description: Invalid or missing note
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/Error"
        '401':
          description: Unauthorized
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/Error"
        '404':
          description: Package not found
  "/v1/api-keys":
    get:
      operationId: listMemberApiKeys
      summary: List member API keys
      description: List member-level API keys. Only accessible with member auth (sk_live_).
      responses:
        '200':
          description: Member API keys
          content:
            application/json:
              schema:
                type: object
                properties:
                  api_keys:
                    type: array
                    items:
                      "$ref": "#/components/schemas/MemberApiKey"
        '401':
          description: Agent credentials cannot manage API keys
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/Error"
    post:
      operationId: createMemberApiKey
      summary: Create member API key
      description: Creates a member-level API key. The raw key is returned once and
        never shown again.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              "$ref": "#/components/schemas/MemberApiKeyRequest"
      responses:
        '201':
          description: Member API key created
          content:
            application/json:
              schema:
                type: object
                properties:
                  api_key:
                    "$ref": "#/components/schemas/MemberApiKey"
                  warning:
                    type: string
        '400':
          description: Validation error or key limit reached
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/Error"
        '401':
          description: Agent credentials cannot create API keys
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/Error"
  "/v1/api-keys/{id}":
    get:
      operationId: getMemberApiKey
      summary: Get member API key details
      parameters:
      - name: id
        in: path
        required: true
        schema:
          type: string
          format: uuid
      responses:
        '200':
          description: Member API key detail
          content:
            application/json:
              schema:
                type: object
                properties:
                  api_key:
                    "$ref": "#/components/schemas/MemberApiKey"
        '401':
          description: Agent credentials cannot manage API keys
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/Error"
        '404':
          description: API key not found
    delete:
      operationId: revokeMemberApiKey
      summary: Revoke member API key
      description: Revokes a member API key. The key used for the current request
        cannot be revoked.
      parameters:
      - name: id
        in: path
        required: true
        schema:
          type: string
          format: uuid
      responses:
        '200':
          description: API key revoked
        '400':
          description: Cannot revoke the key used to authenticate this request
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/Error"
        '401':
          description: Agent credentials cannot manage API keys
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/Error"
        '404':
          description: API key not found
  "/v1/api-keys/{id}/rotate":
    post:
      operationId: rotateMemberApiKey
      summary: Rotate member API key
      description: Revokes the old member API key and creates a new one with the same
        settings. The new raw key is returned once.
      parameters:
      - name: id
        in: path
        required: true
        schema:
          type: string
          format: uuid
      responses:
        '200':
          description: Member API key rotated
          content:
            application/json:
              schema:
                type: object
                properties:
                  api_key:
                    "$ref": "#/components/schemas/MemberApiKey"
                  rotated_from:
                    type: object
                    properties:
                      id:
                        type: string
                        format: uuid
                      prefix:
                        type: string
                  warning:
                    type: string
        '400':
          description: Can only rotate active keys
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/Error"
        '401':
          description: Agent credentials cannot manage API keys
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/Error"
        '404':
          description: API key not found
  "/v1/agents":
    get:
      operationId: listAgents
      summary: List agents
      description: List all agents registered under the authenticated member.
      responses:
        '200':
          description: Agent list
        '401':
          description: Unauthorized
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/Error"
    post:
      operationId: createAgent
      summary: Create an agent
      description: Create a new AI agent. Automatically provisions a mailbox with
        a unique reference code and mailing address.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              "$ref": "#/components/schemas/CreateAgentRequest"
      responses:
        '201':
          description: Agent created with mailbox, reference code, and webhook signing
            key
        '400':
          description: Validation error
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/Error"
  "/v1/agents/{agentId}":
    get:
      operationId: getAgent
      summary: Get agent details
      description: Returns agent details, recent activity, and summary counts.
      parameters:
      - name: agentId
        in: path
        required: true
        schema:
          type: string
          format: uuid
      responses:
        '200':
          description: Agent detail with stats
        '401':
          description: Unauthorized
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/Error"
        '404':
          description: Agent not found
    patch:
      operationId: updateAgent
      summary: Update agent settings
      description: Updates display_name, description, webhook_url, public settings,
        status, or mailbox_md.
      parameters:
      - name: agentId
        in: path
        required: true
        schema:
          type: string
          format: uuid
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                display_name:
                  type: string
                description:
                  type: string
                webhook_url:
                  type: string
                  format: uri
                capabilities:
                  type: object
                public:
                  type: boolean
                status:
                  type: string
                mailbox_md:
                  type:
                  - string
                  - 'null'
      responses:
        '200':
          description: Agent updated
        '400':
          description: Invalid update payload
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/Error"
        '401':
          description: Unauthorized
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/Error"
        '404':
          description: Agent not found
    delete:
      operationId: deleteAgent
      summary: Deactivate an agent
      description: Deactivates the agent, deactivates its mailboxes, and revokes its
        credentials.
      parameters:
      - name: agentId
        in: path
        required: true
        schema:
          type: string
          format: uuid
      responses:
        '200':
          description: Agent deactivated
        '401':
          description: Unauthorized
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/Error"
        '404':
          description: Agent not found
  "/v1/mailboxes":
    get:
      operationId: listMailboxes
      summary: List mailboxes
      description: List all mailboxes with their physical addresses and reference
        codes.
      parameters:
      - name: agent_id
        in: query
        schema:
          type: string
          format: uuid
        description: Filter by agent
      - name: status
        in: query
        schema:
          type: string
        description: Filter by mailbox status
      - name: limit
        in: query
        schema:
          type: integer
          default: 20
          maximum: 100
      - name: offset
        in: query
        schema:
          type: integer
          default: 0
      responses:
        '200':
          description: Mailbox list with addresses
        '401':
          description: Unauthorized
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/Error"
    post:
      operationId: createMailbox
      summary: Provision a mailbox
      description: Creates an additional mailbox for an agent, subject to plan limits.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
              - agent_id
              properties:
                agent_id:
                  type: string
                  format: uuid
                facility_id:
                  type: string
                  format: uuid
      responses:
        '201':
          description: Mailbox provisioned
        '400':
          description: Validation error
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/Error"
        '401':
          description: Unauthorized
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/Error"
        '403':
          description: Plan limit or ownership error
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/Error"
        '404':
          description: Agent or facility not found
  "/v1/agents/{agentId}/instructions":
    get:
      operationId: getAgentInstructions
      summary: Get agent standing instructions (MAILBOX.md)
      description: Returns the renter's MAILBOX.md instructions for the specified
        agent — default handling rules, forwarding preferences, auto-shred criteria,
        scanning options, communication preferences, and more. Agent-scoped keys auto-resolve
        to their own agent (path param ignored). Member keys must specify the agent
        via the path param. Records a version sync on every fetch.
      parameters:
      - name: agentId
        in: path
        required: true
        schema:
          type: string
          format: uuid
        description: Agent ID. Agent-scoped keys (sk_agent_) ignore this and auto-resolve
          to their own agent.
      responses:
        '200':
          description: Agent instructions with version info
          content:
            application/json:
              schema:
                type: object
                properties:
                  mailbox_md:
                    type: string
                    description: Full MAILBOX.md instruction content (Markdown)
                  version:
                    type: integer
                    description: Monotonic version counter. Include as X-Mailbox-MD-Version
                      header on mutating requests.
                  hash:
                    type: string
                    nullable: true
                    description: SHA-256 hex digest of the content
                  updated_at:
                    type: string
                    format: date-time
                    nullable: true
                    description: When the instructions were last updated
        '401':
          description: Unauthorized
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/Error"
        '404':
          description: Agent not found
  "/v1/agents/{agentId}/credentials":
    post:
      operationId: createAgentCredential
      summary: Create agent credential (sk_agent_ or sk_agent_test_ key)
      description: |-
        Create a scoped API key for a specific agent. Only accessible with member auth (sk_live_) — agent keys cannot create more keys. The raw key is returned once in the response and never shown again. Maximum 5 active credentials per agent.

        Pass `environment: 'test'` to create a sandbox key (sk_agent_test_). Test keys work on all production endpoints but skip billing and facility fulfillment. Responses include estimated_live_cost_cents and cost_breakdown so you can verify pricing without incurring charges.
      parameters:
      - name: agentId
        in: path
        required: true
        schema:
          type: string
          format: uuid
        description: Agent ID
      requestBody:
        required: true
        content:
          application/json:
            schema:
              "$ref": "#/components/schemas/AgentCredentialRequest"
      responses:
        '201':
          description: Credential created. The key field is shown once — store it
            immediately.
          content:
            application/json:
              schema:
                type: object
                properties:
                  credential:
                    "$ref": "#/components/schemas/AgentCredential"
                  agent_id:
                    type: string
                    format: uuid
                  agent_name:
                    type: string
                  warning:
                    type: string
        '400':
          description: Invalid scopes or maximum credentials reached
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/Error"
        '401':
          description: Agent keys cannot create credentials — use a member key (sk_live_)
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/Error"
        '404':
          description: Agent not found
    get:
      operationId: listAgentCredentials
      summary: List agent credentials
      description: List all credentials for an agent. Only accessible with member
        auth (sk_live_). Raw keys are never returned — only prefixes.
      parameters:
      - name: agentId
        in: path
        required: true
        schema:
          type: string
          format: uuid
      responses:
        '200':
          description: Credential list
        '401':
          description: Agent keys cannot list credentials — use a member key (sk_live_)
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/Error"
        '404':
          description: Agent not found
  "/v1/agents/{agentId}/credentials/{credentialId}":
    get:
      operationId: getAgentCredential
      summary: Get agent credential details
      parameters:
      - name: agentId
        in: path
        required: true
        schema:
          type: string
          format: uuid
      - name: credentialId
        in: path
        required: true
        schema:
          type: string
          format: uuid
      responses:
        '200':
          description: Credential detail
        '401':
          description: Agent keys cannot manage credentials — use a member key (sk_live_)
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/Error"
        '404':
          description: Credential not found
    delete:
      operationId: revokeAgentCredential
      summary: Revoke agent credential
      description: Permanently revoke an agent credential. Cannot revoke the key being
        used for the current request. Irreversible.
      parameters:
      - name: agentId
        in: path
        required: true
        schema:
          type: string
          format: uuid
      - name: credentialId
        in: path
        required: true
        schema:
          type: string
          format: uuid
      responses:
        '200':
          description: Credential revoked
        '401':
          description: Agent keys cannot revoke credentials — use a member key (sk_live_)
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/Error"
        '404':
          description: Credential not found
  "/v1/agents/{agentId}/expected-shipments":
    get:
      operationId: listExpectedShipments
      summary: List expected shipments
      parameters:
      - name: agentId
        in: path
        required: true
        schema:
          type: string
          format: uuid
      - name: status
        in: query
        schema:
          type: string
      responses:
        '200':
          description: Expected shipment list
        '401':
          description: Unauthorized
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/Error"
        '404':
          description: Agent not found
    post:
      operationId: createExpectedShipment
      summary: Register an expected shipment
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
              - mailbox_id
              properties:
                mailbox_id:
                  type: string
                  format: uuid
                tracking_number:
                  type: string
                carrier:
                  type: string
                description:
                  type: string
                expected_by:
                  type: string
                  format: date-time
                auto_action:
                  type: string
                  enum:
                  - forward
                  - shred
                  - scan
                  - hold
                  - return_to_sender
                  - dispose
                  - open_and_scan
                auto_action_params:
                  type: object
      responses:
        '201':
          description: Expected shipment registered
        '400':
          description: Validation error
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/Error"
        '401':
          description: Unauthorized
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/Error"
        '404':
          description: Agent or mailbox not found
    delete:
      operationId: deleteExpectedShipment
      summary: Cancel an expected shipment
      parameters:
      - name: agentId
        in: path
        required: true
        schema:
          type: string
          format: uuid
      - name: shipment_id
        in: query
        required: true
        schema:
          type: string
          format: uuid
      responses:
        '200':
          description: Expected shipment cancelled
        '400':
          description: Missing shipment_id query parameter
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/Error"
        '401':
          description: Unauthorized
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/Error"
        '404':
          description: Expected shipment not found
  "/v1/agents/{agentId}/rules":
    get:
      operationId: listAgentRules
      summary: List standing instructions
      parameters:
      - name: agentId
        in: path
        required: true
        schema:
          type: string
          format: uuid
      - name: enabled
        in: query
        schema:
          type: boolean
          default: true
      responses:
        '200':
          description: Agent rules
        '401':
          description: Unauthorized
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/Error"
        '404':
          description: Agent not found
    post:
      operationId: createAgentRule
      summary: Create a standing instruction
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
              - name
              - conditions
              - action_type
              properties:
                name:
                  type: string
                description:
                  type: string
                conditions:
                  type: object
                action_type:
                  type: string
                  enum:
                  - forward
                  - shred
                  - scan
                  - hold
                  - return_to_sender
                  - dispose
                  - photograph
                  - open_and_scan
                action_params:
                  type: object
                requires_approval:
                  type: boolean
                  default: false
                priority:
                  type: integer
                  default: 0
                enabled:
                  type: boolean
                  default: true
      responses:
        '201':
          description: Rule created
        '400':
          description: Validation error
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/Error"
        '401':
          description: Unauthorized
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/Error"
        '404':
          description: Agent not found
    patch:
      operationId: updateAgentRule
      summary: Update a standing instruction
      parameters:
      - name: agentId
        in: path
        required: true
        schema:
          type: string
          format: uuid
      - name: rule_id
        in: query
        required: true
        schema:
          type: string
          format: uuid
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                name:
                  type: string
                description:
                  type: string
                conditions:
                  type: object
                action_type:
                  type: string
                  enum:
                  - forward
                  - shred
                  - scan
                  - hold
                  - return_to_sender
                  - dispose
                  - photograph
                  - open_and_scan
                action_params:
                  type: object
                requires_approval:
                  type: boolean
                priority:
                  type: integer
                enabled:
                  type: boolean
      responses:
        '200':
          description: Rule updated
        '400':
          description: Invalid update payload
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/Error"
        '401':
          description: Unauthorized
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/Error"
        '404':
          description: Rule not found
    delete:
      operationId: deleteAgentRule
      summary: Delete a standing instruction
      parameters:
      - name: agentId
        in: path
        required: true
        schema:
          type: string
          format: uuid
      - name: rule_id
        in: query
        required: true
        schema:
          type: string
          format: uuid
      responses:
        '200':
          description: Rule deleted
        '400':
          description: Missing rule_id query parameter
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/Error"
        '401':
          description: Unauthorized
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/Error"
        '404':
          description: Rule not found
  "/v1/messages":
    get:
      operationId: listSupportMessages
      summary: List support conversations
      parameters:
      - name: status
        in: query
        schema:
          type: string
      - name: limit
        in: query
        schema:
          type: integer
          default: 20
          maximum: 100
      - name: offset
        in: query
        schema:
          type: integer
          default: 0
      responses:
        '200':
          description: Support conversation list
        '401':
          description: Unauthorized
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/Error"
    post:
      operationId: createSupportMessage
      summary: Send a support message
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
              - category
              - body
              properties:
                category:
                  type: string
                  enum:
                  - billing
                  - support
                  - feature
                  - account
                  - other
                body:
                  type: string
      responses:
        '201':
          description: Support conversation created
        '400':
          description: Invalid category or body
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/Error"
        '401':
          description: Unauthorized
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/Error"
  "/v1/messages/{id}":
    get:
      operationId: getSupportMessage
      summary: Get support conversation thread
      parameters:
      - name: id
        in: path
        required: true
        schema:
          type: string
          format: uuid
      responses:
        '200':
          description: Support conversation thread
        '401':
          description: Unauthorized
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/Error"
        '404':
          description: Message not found
  "/v1/webhooks/settings":
    get:
      operationId: getWebhookSettings
      summary: Get webhook configuration
      description: Returns the current webhook configuration for an agent, including
        URL, auth mode, event filters, and active signing keys.
      parameters:
      - name: agent_id
        in: query
        schema:
          type: string
          format: uuid
        description: Required for member keys (sk_live_). Agent keys auto-resolve.
      responses:
        '200':
          description: Webhook configuration
          content:
            application/json:
              schema:
                type: object
                properties:
                  webhook_url:
                    type: string
                    nullable: true
                  enabled:
                    type: boolean
                  event_types:
                    type: array
                    items:
                      type: string
                  carrier_filter:
                    type: array
                    items:
                      type: string
                    nullable: true
                  min_weight_oz:
                    type: number
                    nullable: true
                  auth_type:
                    type: string
                    enum:
                    - hmac
                    - bearer
                    - header
                  payload_format:
                    type: string
                    enum:
                    - standard
                    - openclaw
                  signing_keys:
                    type: array
                    items:
                      type: object
                      properties:
                        id:
                          type: string
                          format: uuid
                        key_prefix:
                          type: string
                        status:
                          type: string
                        primary_key:
                          type: boolean
                        created_at:
                          type: string
                          format: date-time
                        expires_at:
                          type: string
                          format: date-time
                          nullable: true
        '401':
          description: Unauthorized
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/Error"
    put:
      operationId: updateWebhookSettings
      summary: Update webhook settings
      description: Update webhook delivery configuration for an agent. All fields
        optional. Setting a webhook_url on an agent with no signing key auto-generates
        one and returns it in the response.
      requestBody:
        content:
          application/json:
            schema:
              type: object
              properties:
                agent_id:
                  type: string
                  format: uuid
                  description: Required for member keys (sk_live_). Agent keys auto-resolve.
                webhook_url:
                  type: string
                  format: uri
                  nullable: true
                  description: HTTPS URL (or null to disable)
                enabled:
                  type: boolean
                event_types:
                  type: array
                  items:
                    type: string
                  description: Array of event types or ["*"] for all
                carrier_filter:
                  type: array
                  items:
                    type: string
                  nullable: true
                min_weight_oz:
                  type: number
                  nullable: true
                auth_type:
                  type: string
                  enum:
                  - hmac
                  - bearer
                  - header
                  description: Webhook authentication mode
                auth_token:
                  type: string
                  description: Token for bearer/header auth (min 16 chars)
                auth_header:
                  type: string
                  description: Custom header name for header auth mode
                payload_format:
                  type: string
                  enum:
                  - standard
                  - openclaw
                  description: Payload structure format
      responses:
        '200':
          description: Updated webhook settings with signing key (if auto-generated)
        '400':
          description: Validation error (invalid URL, missing auth_token for bearer
            mode, etc.)
        '401':
          description: Unauthorized
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/Error"
  "/v1/webhooks/signing-keys/rotate":
    post:
      operationId: rotateWebhookSigningKey
      summary: Rotate webhook signing key
      description: Revokes the current active signing key and generates a new one.
        The new raw secret is returned once — store it immediately and update your
        webhook verification code. If no signing key exists yet (e.g. agent created
        without webhook_url), generates the first key. The old key's prefix changes,
        so use the prefix in X-Mailbox-Signature to look up which secret to verify
        against during the transition.
      requestBody:
        content:
          application/json:
            schema:
              type: object
              properties:
                agent_id:
                  type: string
                  format: uuid
                  description: Required for member keys (sk_live_). Agent keys auto-resolve.
      responses:
        '200':
          description: New signing key generated. Store the secret immediately.
          content:
            application/json:
              schema:
                type: object
                properties:
                  signing_key:
                    type: object
                    properties:
                      prefix:
                        type: string
                        description: Key prefix (e.g. whsk_a1b2c3d4)
                      secret:
                        type: string
                        description: 64-char hex HMAC secret — shown once
                  rotated_from:
                    type: object
                    nullable: true
                    properties:
                      id:
                        type: string
                        format: uuid
                      prefix:
                        type: string
                    description: Previous key info (null if this is the first key)
                  agent_id:
                    type: string
                    format: uuid
                  warning:
                    type: string
        '404':
          description: Agent not found
  "/v1/webhooks/events":
    get:
      operationId: listWebhookEvents
      summary: List webhook delivery events
      description: List recent webhook delivery events with filtering and pagination.
        Agent-scoped keys auto-filter to their own agent's events. Use status filter
        to find failed deliveries for debugging. Facility keys cannot access this
        endpoint.
      parameters:
      - name: agent_id
        in: query
        schema:
          type: string
          format: uuid
        description: Filter by agent. Required for member keys (sk_live_) to scope
          results. Agent keys (sk_agent_) auto-filter and ignore this.
      - name: status
        in: query
        schema:
          type: string
          enum:
          - pending
          - delivered
          - failed
        description: Filter by delivery status
      - name: event_type
        in: query
        schema:
          type: string
        description: Filter by event type (e.g. package.received, action.completed)
      - name: limit
        in: query
        schema:
          type: integer
          default: 20
          maximum: 100
      - name: offset
        in: query
        schema:
          type: integer
          default: 0
      responses:
        '200':
          description: Webhook events with pagination
        '401':
          description: Unauthorized
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/Error"
        '403':
          description: Forbidden (facility keys)
  "/v1/webhooks/events/{eventId}":
    get:
      operationId: getWebhookEvent
      summary: Get webhook event with delivery attempts
      description: Returns a single webhook event and its full delivery attempt history,
        including HTTP status codes, response times, and error messages. Use this
        to debug delivery failures.
      parameters:
      - name: eventId
        in: path
        required: true
        schema:
          type: string
          format: uuid
      responses:
        '200':
          description: Webhook event with delivery attempt history
        '401':
          description: Unauthorized
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/Error"
        '404':
          description: Webhook event not found
  "/v1/usage":
    get:
      operationId: getUsage
      summary: Get usage and billing summary
      description: Get usage events and billing summary for the current period.
      parameters:
      - name: period_start
        in: query
        schema:
          type: string
          format: date-time
      - name: period_end
        in: query
        schema:
          type: string
          format: date-time
      responses:
        '200':
          description: Usage summary and events
        '401':
          description: Unauthorized
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/Error"
  "/v1/mail":
    post:
      operationId: submitOutboundMail
      summary: Submit outbound mail
      description: |-
        Submit a document for printing and postal mailing. Supported local-only formats are PDF, DOCX, JPG, PNG, TXT, and CSV. The original uploaded file is stored in mailbox.bot storage and previewed through a same-origin print view; no third-party conversion service is used. PDF uploads are WYSIWYG. For DOCX, TXT, and CSV, you may supply page_count to make pricing deterministic; without it, DOCX uses embedded Office page metadata when available and TXT/CSV use local layout estimation.

        **IMPORTANT — Immediate charge:** With a production key (sk_agent_), this endpoint charges the member's card on file immediately upon submission. Use dry_run=true to validate and preview cost without charging. Use requires_approval=true to defer charging until the member approves in their dashboard.

        **Sandbox mode:** When called with a test key (sk_agent_test_), the full pipeline runs (PDF validation, page counting, cost calculation, HMAC-signed webhook) but no Stripe charge is applied and no facility fulfillment is queued. Response includes test_mode: true, cost_cents: 0, estimated_live_cost_cents (what production would charge), and cost_breakdown (printing, postage, color surcharge per page).

        **Response header:** All agent-key requests include an `X-Test-Mode: true|false` response header for middleware/observability detection without parsing the JSON body. The `test_mode` field is always present in both live and sandbox responses (never omitted).

        **Key policies (apply to live and sandbox keys equally):** If the credential was minted with `force_approval: true`, the submission is routed to `pending_approval` regardless of the `requires_approval` body parameter. If `max_daily_pieces` is set on the credential and the limit has been reached in the trailing 24h, the submission is rejected with 422. Both policies surface as `warnings` in the `dry_run` response so an agent can detect them before committing.
      parameters:
      - name: X-Mailbox-MD-Version
        in: header
        required: true
        schema:
          type: integer
        description: Agent's current MAILBOX.md version. Returns 400 if missing, 409
          if stale.
      - name: Idempotency-Key
        in: header
        required: false
        schema:
          type: string
        description: Unique key for safe retries. Same key within 24h returns cached
          response. Concurrent duplicate requests return 409.
      - name: X-Max-Cost-Cents
        in: header
        required: false
        schema:
          type: integer
        description: Optional cost cap. If the computed cost exceeds this value, the
          request is rejected with 422 before any charge is made. Prevents accidental
          expensive mailings.
      requestBody:
        required: true
        content:
          multipart/form-data:
            schema:
              type: object
              required:
              - document
              - recipient_name
              - recipient_line1
              - recipient_city
              - recipient_state
              - recipient_zip
              properties:
                document:
                  type: string
                  format: binary
                  description: 'Document file. Supported formats: PDF, DOCX, JPG,
                    PNG, TXT, CSV. Max 10MB.'
                page_count:
                  type: integer
                  minimum: 1
                  maximum: 1000
                  description: Optional explicit page count for non-PDF uploads when
                    exact pagination is already known. When supplied for DOCX, TXT,
                    or CSV, it overrides local page-count detection and makes pricing
                    deterministic.
                recipient_name:
                  type: string
                recipient_line1:
                  type: string
                recipient_line2:
                  type: string
                recipient_city:
                  type: string
                recipient_state:
                  type: string
                  description: 2-letter state code
                recipient_zip:
                  type: string
                  description: 5 or 5+4 digit ZIP
                recipient_country:
                  type: string
                  default: US
                return_name:
                  type: string
                  description: Return address name. Defaults to member profile if
                    omitted
                return_line1:
                  type: string
                return_line2:
                  type: string
                  description: Return address line 2 (suite, unit, etc.)
                return_city:
                  type: string
                return_state:
                  type: string
                return_zip:
                  type: string
                mail_class:
                  type: string
                  enum:
                  - first_class
                  - priority
                  - certified
                  - certified_return_receipt
                  - fedex_ground
                  - fedex_express
                  - fedex_2day
                  - fedex_overnight
                  - ups_ground
                  - ups_2day
                  - ups_next_day
                  default: first_class
                color:
                  type: boolean
                  default: false
                duplex:
                  type: boolean
                  default: false
                package_id:
                  type: string
                  format: uuid
                  description: Link to inbound package
                inbound_capture_id:
                  type: string
                  format: uuid
                  description: Optional inbound mail item to reply to. mailbox.bot
                    attaches the outbound send to that item's physical-mail thread
                    when safe.
                postal_mail_thread_id:
                  type: string
                  format: uuid
                  description: Optional physical-mail thread to attach this outbound
                    mail to.
                agent_notes:
                  type: string
                  description: Instructions for the facility operator
                requires_approval:
                  type: boolean
                  default: false
                  description: If true, member must approve in dashboard before mail
                    enters facility queue
                metadata:
                  type: string
                  description: JSON-encoded object string. In multipart/form-data
                    this field must be sent as a string such as {"workflow_id":"wf_123","correlation_id":"abc"}.
                    The parsed object is echoed in GET responses and webhook payloads.
                dry_run:
                  type: boolean
                  default: false
                  description: If true, validate inputs and return cost breakdown
                    without uploading, creating a record, or charging. Returns 200
                    with cost preview.
      responses:
        '200':
          description: 'Dry-run response (when dry_run=true). Returns cost_cents,
            cost_display, cost_breakdown, page_count, mail_class, color, duplex, dry_run:
            true, and warnings (string array of key-policy notices, e.g. force_approval
            active, daily piece limit). No record created, no charge applied.'
        '201':
          description: 'Outbound mail submitted. Response includes id, status, cost_cents,
            cost_display, cost_breakdown, test_mode (always present), document_url,
            document_format, document_filename, pdf_url (PDF only), and metadata.
            With test keys: also includes estimated_live_cost_cents.'
        '400':
          description: Validation error (missing fields, invalid page_count, unsupported
            file format, bad state/zip, or unable to determine DOCX page count locally)
            or MAILBOX_MD_VERSION_REQUIRED (missing version header). Validation failures
            include a structured fields[] array.
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/Error"
        '401':
          description: Unauthorized
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/Error"
        '402':
          description: Payment Required — card declined, no valid payment method on
            file, or member account suspended. The outbound mail record is created
            but immediately set to 'failed' status.
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/Error"
        '404':
          description: No active mailbox for this agent
        '409':
          description: MAILBOX_MD_VERSION_MISMATCH — agent's version is stale, re-fetch
            instructions and retry
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/Error"
        '422':
          description: Cost exceeds X-Max-Cost-Cents limit, per-transaction platform
            limit, daily spend cap, or the credential's max_daily_pieces policy. No
            charge made.
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/Error"
    get:
      operationId: listOutboundMail
      summary: List outbound mail
      description: List outbound mail jobs with status filtering and pagination.
      parameters:
      - name: status
        in: query
        schema:
          type: string
          enum:
          - pending_approval
          - submitted
          - ready
          - mailed
          - delivered
          - failed
          - cancelled
      - name: limit
        in: query
        schema:
          type: integer
          default: 20
          maximum: 100
      - name: offset
        in: query
        schema:
          type: integer
          default: 0
      responses:
        '200':
          description: Outbound mail list with pagination
  "/v1/inbound":
    get:
      operationId: listInboundMail
      summary: List forwarded inbound mail context
      description: 'Lists inbound mail items created when an operator forwards scans,
        photos, PDFs, provider notices, notes, or other context-aware documents from
        an address they already use to a private mailbox.bot alias. This is not a
        newly assigned physical mailing address API. Default response is compact: subject/body notes,
        category, suggested action, thread hint, attachment metadata, extracted fields,
        and top context fields. Use include=drafting for LLM-ready outbound reply
        context, and include=ocr,lineage,files only when the agent needs deeper source
        context.'
      parameters:
      - name: limit
        in: query
        schema:
          type: integer
          default: 20
          maximum: 100
      - name: offset
        in: query
        schema:
          type: integer
          default: 0
      - name: category
        in: query
        schema:
          type: string
      - name: status
        in: query
        schema:
          type: string
          enum:
          - captured
          - stored
          - extracted
          - needs_review
          - failed
      - name: thread_id
        in: query
        schema:
          type: string
          format: uuid
      - name: include
        in: query
        schema:
          type: string
          example: drafting,files,ocr,lineage
        description: 'Comma-separated expansions: drafting, files, ocr, lineage, signed_urls.'
      responses:
        '200':
          description: Inbound mail list with compact context and pagination
          content:
            application/json:
              schema:
                type: object
                properties:
                  inbound_mail:
                    type: array
                    items:
                      "$ref": "#/components/schemas/InboundMailItem"
                  pagination:
                    type: object
  "/v1/inbound-forwarding-addresses":
    get:
      operationId: listInboundForwardingAddresses
      summary: List inbound forwarding aliases
      description: Returns the private forwarding aliases this account can use to
        forward/email scans, PDFs, photos, provider notices, notes, and other context-aware
        documents into mailbox.bot inbound context. Forwarding/emailing attachments
        to the alias initiates OCR/extraction; REST and MCP tools retrieve captured
        context and do not upload files directly into OCR. mailbox.bot may create
        a default alias on first retrieval. These aliases are intake email addresses,
        not new physical mailing addresses. The alias is member-scoped, so live and
        sandbox agent keys for the same member resolve to the same intake address.
      responses:
        '200':
          description: Inbound forwarding aliases
          content:
            application/json:
              schema:
                type: object
                properties:
                  forwarding_addresses:
                    type: array
                    items:
                      "$ref": "#/components/schemas/InboundForwardingAddress"
  "/v1/inbound/{id}":
    get:
      operationId: getInboundMail
      summary: Get forwarded inbound mail context
      description: 'Returns one forwarded inbound mail item. By default this avoids
        OCR firehose output and returns clean agent context: extracted sender/address/reference
        fields, suggested action, thread hint, and file summaries. Use include=drafting
        for LLM-ready reply context and outbound linkage, include=ocr,lineage to inspect
        OCR/provenance, and signed_urls=true only when the agent needs temporary access
        to private originals. The same item is readable from live or sandbox agent
        keys when those keys belong to the same member and agent scope.'
      parameters:
      - name: id
        in: path
        required: true
        schema:
          type: string
          format: uuid
      - name: include
        in: query
        schema:
          type: string
          example: drafting,files,ocr,lineage
      - name: signed_urls
        in: query
        schema:
          type: boolean
          default: false
      responses:
        '200':
          description: Inbound mail item with context
          content:
            application/json:
              schema:
                type: object
                properties:
                  inbound_mail:
                    "$ref": "#/components/schemas/InboundMailItem"
        '404':
          description: Not found
  "/v1/postal-threads":
    get:
      operationId: listPostalThreads
      summary: List physical-mail threads
      description: Lists durable physical-mail threads that group inbound scans, human
        notes, and outbound mail.
      parameters:
      - name: limit
        in: query
        schema:
          type: integer
          default: 20
          maximum: 100
      - name: offset
        in: query
        schema:
          type: integer
          default: 0
      - name: category
        in: query
        schema:
          type: string
      - name: status
        in: query
        schema:
          type: string
          enum:
          - open
          - waiting
          - closed
          - archived
      - name: include
        in: query
        schema:
          type: string
          example: events
        description: Comma-separated expansions. Use include=events for thread timeline.
      responses:
        '200':
          description: Postal thread list
          content:
            application/json:
              schema:
                type: object
                properties:
                  postal_threads:
                    type: array
                    items:
                      "$ref": "#/components/schemas/PostalThread"
                  pagination:
                    type: object
  "/v1/postal-threads/{id}":
    get:
      operationId: getPostalThread
      summary: Get physical-mail thread
      description: Returns one physical-mail thread. Use include=events to include
        inbound/outbound timeline references.
      parameters:
      - name: id
        in: path
        required: true
        schema:
          type: string
          format: uuid
      - name: include
        in: query
        schema:
          type: string
          example: events
      responses:
        '200':
          description: Postal thread
          content:
            application/json:
              schema:
                type: object
                properties:
                  postal_thread:
                    "$ref": "#/components/schemas/PostalThread"
        '404':
          description: Not found
  "/v1/mail/{id}":
    get:
      operationId: getOutboundMail
      summary: Get outbound mail details
      description: Canonical polling endpoint for outbound mail status. Returns current
        status, tracking, carrier, timestamps, return address, metadata, a freshly-generated
        signed document/PDF URL (1-hour expiry), and fulfillment_photos with fresh
        signed URLs. Agents can use this if no webhook is configured, or after a webhook
        via callback_url.
      parameters:
      - name: id
        in: path
        required: true
        schema:
          type: string
          format: uuid
      responses:
        '200':
          description: Outbound mail details with PDF URL
        '404':
          description: Not found
    delete:
      operationId: cancelOutboundMail
      summary: Cancel outbound mail
      description: Cancel outbound mail that is still in 'submitted' status. Returns
        409 if already ready or mailed.
      parameters:
      - name: id
        in: path
        required: true
        schema:
          type: string
          format: uuid
      responses:
        '200':
          description: Mail cancelled
        '409':
          description: Cannot cancel — already ready or mailed
  "/v1/test/webhook":
    post:
      operationId: testWebhook
      summary: Fire a test webhook event
      description: Sends a sample event payload to the agent's configured webhook
        endpoint. Use this to exercise your webhook handler without moving real mail.
        Requires a configured webhook URL (PUT /v1/webhooks/settings).
      requestBody:
        content:
          application/json:
            schema:
              type: object
              properties:
                event_type:
                  type: string
                  enum:
                  - mail.pending_approval
                  - mail.submitted
                  - mail.ready
                  - mail.mailed
                  - mail.delivered
                  - mail.failed
                  - mail.cancelled
                  - package.received
                  - action.created
                  - action.completed
                  default: mail.submitted
                  description: Event type to simulate
      responses:
        '200':
          description: Test event queued for delivery. Returns queued, event_type,
            webhook_url, sample_payload.
        '422':
          description: No webhook URL configured for this agent
  "/v1/test/packages":
    post:
      operationId: testCreatePackage
      summary: Create a test address/package beta item
      description: Creates a fake package record in the agent's active real mailing
        address/package beta surface and fires a package.received webhook. This helper is for account-enabled
        package/receiving integrations, not the default inbound forwarding alias/OCR
        workflow.
      requestBody:
        content:
          application/json:
            schema:
              type: object
              properties:
                carrier:
                  type: string
                  default: USPS
                tracking_number:
                  type: string
                  description: Defaults to TEST{timestamp}
                weight_oz:
                  type: number
                  default: 4
                notes:
                  type: string
      responses:
        '201':
          description: 'Test mailpiece created. Returns package object with test_mode:
            true.'
        '404':
          description: No active mailbox for this agent
  "/v1/test/mail":
    post:
      operationId: testCreateOutboundMail
      summary: Create a test outbound mail record
      description: Creates a test_mode=true outbound mail record without requiring
        a real PDF. No charge is applied (cost_cents=0), but the response includes
        estimated_live_cost_cents showing what the equivalent production job would
        cost based on page count, mail class, color, and destination. Fires a mail.submitted
        webhook. Advance the lifecycle via POST /v1/test/mail/:id/advance.
      requestBody:
        content:
          application/json:
            schema:
              type: object
              properties:
                recipient_name:
                  type: string
                  default: Test Recipient
                recipient_line1:
                  type: string
                  default: 123 Test Street
                recipient_city:
                  type: string
                  default: San Francisco
                recipient_state:
                  type: string
                  default: CA
                recipient_zip:
                  type: string
                  default: '94105'
                  description: Destination ZIP — affects postage zone and estimated
                    cost
                mail_class:
                  type: string
                  enum:
                  - first_class
                  - priority
                  - certified
                  - certified_return_receipt
                  - fedex_ground
                  - fedex_express
                  - fedex_2day
                  - fedex_overnight
                  - ups_ground
                  - ups_2day
                  - ups_next_day
                  default: first_class
                page_count:
                  type: integer
                  minimum: 1
                  maximum: 100
                  default: 1
                  description: Number of pages — affects printing cost, weight, and
                    postage
                color:
                  type: boolean
                  default: false
                  description: Color printing — adds per-page surcharge to estimated
                    cost
                agent_notes:
                  type: string
                metadata:
                  type: object
                  description: Arbitrary key-value pairs echoed in responses and webhooks
      responses:
        '201':
          description: Test outbound mail created (test_mode=true, cost_cents=0).
            Returns outbound_mail object with estimated_live_cost_cents and cost_breakdown
            showing printing, postage, and color surcharge.
        '404':
          description: No active mailbox for this agent
  "/v1/test/mail/{id}/advance":
    post:
      operationId: testAdvanceOutboundMail
      summary: Advance test outbound mail status
      description: |-
        Advances a test_mode outbound mail record by one step in the lifecycle (submitted → ready → mailed → delivered) and fires the corresponding webhook with full facility fulfillment data.

        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 realistic facility behavior: ready adds fulfillment photos (pages + envelope), mailed assigns carrier-format tracking number + dispatch method + receipt photo, delivered sets final timestamp. Webhooks include fulfillment_photos array with URLs, and the dashboard progress tracker renders all photos and tracking data.
      parameters:
      - name: id
        in: path
        required: true
        schema:
          type: string
          format: uuid
      responses:
        '200':
          description: Status advanced. Returns previous_status, new status, tracking_number,
            carrier, dispatch_method, claimed_at, mailed_at, delivered_at, and fulfillment_photos
            count.
        '403':
          description: Record is not test_mode — use POST /v1/test/mail or an sk_agent_test_
            key to create a test record first
        '404':
          description: Outbound mail not found
        '409':
          description: Status already terminal (delivered, failed, cancelled)
