openapi: 3.1.0
info:
  title: KriyAI Agent Mail API
  version: 0.1.0
  description: Agent-first email API for KriyAI customer deployments.
servers:
  - url: https://mail-api.noinfra.ai
  - url: http://localhost:4080
components:
  securitySchemes:
    ApiKeyAuth:
      type: apiKey
      in: header
      name: x-api-key
security:
  - ApiKeyAuth: []
paths:
  /mcp:
    get:
      summary: Describe the hosted MCP endpoint
      description: Returns basic transport metadata for AI clients. Use POST /mcp for JSON-RPC MCP calls.
      responses:
        "200":
          description: MCP endpoint metadata
    post:
      summary: Hosted MCP JSON-RPC endpoint
      description: Supports initialize, tools/list, and tools/call. Tools cover inboxes, messages, replies, threads, drafts, and attachments, using the same API-key authorization model as REST.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              oneOf:
                - type: object
                - type: array
                  items:
                    type: object
      responses:
        "200":
          description: JSON-RPC response or batch response
  /v1/protocols/smtp/session:
    get:
      summary: Validate an SMTP submission login
      description: Used by the SMTP submission worker. The bearer API key must include messages:send and must be authorized for the inbox address supplied as username.
      parameters:
        - in: query
          name: username
          required: true
          description: Full inbox email address used as the SMTP username.
          schema:
            type: string
            format: email
      responses:
        "200":
          description: Authorized SMTP session metadata
          content:
            application/json:
              schema:
                type: object
                required: [inboxId, address, customerId]
                properties:
                  inboxId:
                    type: string
                  address:
                    type: string
                    format: email
                  customerId:
                    type: string
                  deploymentId:
                    type: string
                  agentId:
                    type: string
        "400":
          description: Missing username
        "403":
          description: API key is missing messages:send or is not authorized for the inbox
        "404":
          description: SMTP username does not match an active inbox
  /v1/protocols/imap/session:
    get:
      summary: Validate an IMAP login
      description: Used by the IMAP worker. The bearer API key must include messages:read and must be authorized for the inbox address supplied as username.
      parameters:
        - in: query
          name: username
          required: true
          description: Full inbox email address used as the IMAP username.
          schema:
            type: string
            format: email
      responses:
        "200":
          description: Authorized IMAP session metadata
          content:
            application/json:
              schema:
                type: object
                required: [inboxId, address, customerId]
                properties:
                  inboxId:
                    type: string
                  address:
                    type: string
                    format: email
                  customerId:
                    type: string
                  deploymentId:
                    type: string
                  agentId:
                    type: string
        "400":
          description: Missing username
        "403":
          description: API key is missing messages:read or is not authorized for the inbox
        "404":
          description: IMAP username does not match an active inbox
  /v1/health:
    get:
      summary: Health check
      security: []
      responses:
        "200":
          description: Service health
  /v1/readiness:
    get:
      summary: Production readiness check
      security: []
      responses:
        "200":
          description: Readiness report. Inspect the ready field for production readiness.
  /v1/customers:
    post:
      summary: Create a customer tenant
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [name]
              properties:
                name:
                  type: string
                externalId:
                  type: string
      responses:
        "201":
          description: Customer created
  /v1/customers/{customerId}/activity:
    get:
      summary: Get monitoring counters and recent activity for a customer
      description: Customer-scoped endpoint that rolls up message counters, freshness timestamps, workflow queue counters, per-deployment counters including zero-message deployments, and recent messages across all deployments for one customer. Inbox-scoped keys cannot access customer-wide activity.
      parameters:
        - in: path
          name: customerId
          required: true
          schema:
            type: string
      responses:
        "200":
          description: Customer activity summary with aggregate counters, per-deployment counters, and recent messages
        "403":
          description: API key is not authorized for customer-wide activity
  /v1/customers/{customerId}/deployments:
    get:
      summary: List deployments for a customer
      description: Customer-scoped endpoint that returns deployment records for orchestration rediscovery. Inbox-scoped keys cannot access customer-wide deployment rosters.
      parameters:
        - in: path
          name: customerId
          required: true
          schema:
            type: string
        - in: query
          name: q
          description: Case-insensitive search across deployment id, external id, and name.
          schema:
            type: string
        - in: query
          name: name
          description: Case-insensitive deployment name substring filter.
          schema:
            type: string
        - in: query
          name: externalId
          description: Exact external id filter.
          schema:
            type: string
        - in: query
          name: since
          description: Alias for createdAfter. Returns deployments created after this ISO timestamp.
          schema:
            type: string
            format: date-time
        - in: query
          name: createdAfter
          schema:
            type: string
            format: date-time
        - in: query
          name: createdBefore
          schema:
            type: string
            format: date-time
        - in: query
          name: limit
          schema:
            type: integer
            minimum: 1
            maximum: 250
        - in: query
          name: cursor
          description: Opaque cursor returned as nextCursor from a previous limited response.
          schema:
            type: string
      responses:
        "200":
          description: Customer deployments with optional nextCursor
          content:
            application/json:
              schema:
                type: object
                required: [data]
                properties:
                  data:
                    type: array
                    items:
                      type: object
                  nextCursor:
                    type: string
        "403":
          description: API key is not authorized for customer deployments
  /v1/customers/{customerId}/agents:
    get:
      summary: List agents for a customer
      description: Customer-scoped endpoint that returns agent records across deployments, including long-lived default inbox ids and email addresses when assigned. Inbox-scoped keys cannot access customer-wide agent rosters.
      parameters:
        - in: path
          name: customerId
          required: true
          schema:
            type: string
        - in: query
          name: q
          description: Case-insensitive search across agent id, external id, name, role, default email address, default inbox id, and deployment id.
          schema:
            type: string
        - in: query
          name: deploymentId
          description: Exact deployment id filter.
          schema:
            type: string
        - in: query
          name: role
          description: Exact normalized role filter.
          schema:
            type: string
        - in: query
          name: externalId
          description: Exact external id filter.
          schema:
            type: string
        - in: query
          name: name
          description: Case-insensitive agent name substring filter.
          schema:
            type: string
        - in: query
          name: hasDefaultInbox
          description: Set to false to find agents without an assigned default inbox.
          schema:
            type: boolean
        - in: query
          name: since
          description: Alias for createdAfter. Returns agents created after this ISO timestamp.
          schema:
            type: string
            format: date-time
        - in: query
          name: createdAfter
          schema:
            type: string
            format: date-time
        - in: query
          name: createdBefore
          schema:
            type: string
            format: date-time
        - in: query
          name: limit
          schema:
            type: integer
            minimum: 1
            maximum: 250
        - in: query
          name: cursor
          description: Opaque cursor returned as nextCursor from a previous limited response.
          schema:
            type: string
      responses:
        "200":
          description: Customer agents with optional nextCursor
          content:
            application/json:
              schema:
                type: object
                required: [data]
                properties:
                  data:
                    type: array
                    items:
                      type: object
                  nextCursor:
                    type: string
        "403":
          description: API key is not authorized for customer agents
  /v1/customers/{customerId}/inboxes:
    get:
      summary: List agent inboxes for a customer
      description: Customer-scoped endpoint that returns long-lived agent inboxes across deployments. Inbox-scoped keys cannot access customer-wide inbox rosters.
      parameters:
        - in: path
          name: customerId
          required: true
          schema:
            type: string
        - in: query
          name: q
          description: Case-insensitive search across inbox id, address, display name, deployment id, and agent id.
          schema:
            type: string
        - in: query
          name: deploymentId
          description: Exact deployment id filter.
          schema:
            type: string
        - in: query
          name: agentId
          description: Exact agent id filter.
          schema:
            type: string
        - in: query
          name: status
          schema:
            type: string
            enum: [active, suspended]
        - in: query
          name: address
          description: Case-insensitive inbox address substring filter.
          schema:
            type: string
        - in: query
          name: domain
          description: Inbox address domain suffix filter.
          schema:
            type: string
        - in: query
          name: displayName
          description: Case-insensitive display name substring filter.
          schema:
            type: string
        - in: query
          name: since
          description: Alias for createdAfter. Returns inboxes created after this ISO timestamp.
          schema:
            type: string
            format: date-time
        - in: query
          name: createdAfter
          schema:
            type: string
            format: date-time
        - in: query
          name: createdBefore
          schema:
            type: string
            format: date-time
        - in: query
          name: limit
          schema:
            type: integer
            minimum: 1
            maximum: 250
        - in: query
          name: cursor
          description: Opaque cursor returned as nextCursor from a previous limited response.
          schema:
            type: string
      responses:
        "200":
          description: Customer inboxes with optional nextCursor
          content:
            application/json:
              schema:
                type: object
                required: [data]
                properties:
                  data:
                    type: array
                    items:
                      type: object
                  nextCursor:
                    type: string
        "403":
          description: API key is not authorized for customer inboxes
  /v1/customers/{customerId}/messages:
    get:
      summary: List customer messages across all deployments
      description: Customer-scoped endpoint that returns a filtered cross-deployment mail feed for one customer. Inbox-scoped keys cannot access customer-wide messages.
      parameters:
        - in: path
          name: customerId
          required: true
          schema:
            type: string
        - in: query
          name: q
          description: Case-insensitive text search across sender, recipients, subject, plain text, and HTML.
          schema:
            type: string
        - in: query
          name: from
          description: Case-insensitive sender substring filter.
          schema:
            type: string
        - in: query
          name: to
          description: Case-insensitive recipient substring filter across to, cc, and bcc.
          schema:
            type: string
        - in: query
          name: subject
          description: Case-insensitive subject substring filter.
          schema:
            type: string
        - in: query
          name: direction
          schema:
            type: string
            enum: [inbound, outbound]
        - in: query
          name: status
          schema:
            type: string
        - in: query
          name: intent
          schema:
            type: string
        - in: query
          name: urgency
          schema:
            type: string
        - in: query
          name: routeHint
          schema:
            type: string
        - in: query
          name: label
          description: Classification label that must be present. Repeat to require multiple labels.
          schema:
            type: string
        - in: query
          name: needsHumanReview
          schema:
            type: boolean
        - in: query
          name: workflowState
          schema:
            type: string
            enum: [open, claimed, resolved]
        - in: query
          name: assignedTo
          schema:
            type: string
        - in: query
          name: snoozed
          schema:
            type: boolean
        - in: query
          name: snoozedBy
          schema:
            type: string
        - in: query
          name: claimedBy
          schema:
            type: string
        - in: query
          name: resolvedBy
          schema:
            type: string
        - in: query
          name: reopenedBy
          schema:
            type: string
        - in: query
          name: availableForClaim
          schema:
            type: boolean
        - in: query
          name: since
          description: Alias for createdAfter. Returns messages created after this ISO timestamp.
          schema:
            type: string
            format: date-time
        - in: query
          name: createdAfter
          schema:
            type: string
            format: date-time
        - in: query
          name: createdBefore
          schema:
            type: string
            format: date-time
        - in: query
          name: limit
          schema:
            type: integer
            minimum: 1
            maximum: 250
        - in: query
          name: cursor
          description: Opaque cursor returned as nextCursor from a previous limited response.
          schema:
            type: string
      responses:
        "200":
          description: Filtered messages across customer deployments
          content:
            application/json:
              schema:
                type: object
                required: [data]
                properties:
                  data:
                    type: array
                    items:
                      type: object
                  nextCursor:
                    type: string
        "403":
          description: API key is not authorized for customer-wide messages
  /v1/customers/{customerId}/threads:
    get:
      summary: List customer conversation threads across deployments
      description: Customer-scoped endpoint that returns conversation threads ordered by latest message activity, including inbox, deployment, agent, and address metadata. Inbox-scoped keys cannot access customer-wide threads.
      parameters:
        - in: path
          name: customerId
          required: true
          schema:
            type: string
        - in: query
          name: q
          description: Case-insensitive search across thread id, subject, inbox id, deployment id, agent id, and inbox address.
          schema:
            type: string
        - in: query
          name: inboxId
          description: Exact inbox id filter.
          schema:
            type: string
        - in: query
          name: deploymentId
          description: Exact deployment id filter.
          schema:
            type: string
        - in: query
          name: agentId
          description: Exact agent id filter.
          schema:
            type: string
        - in: query
          name: address
          description: Case-insensitive inbox address substring filter.
          schema:
            type: string
        - in: query
          name: subject
          description: Case-insensitive subject substring filter.
          schema:
            type: string
        - in: query
          name: label
          description: Label that must be present on the thread. Repeat to require multiple labels.
          schema:
            type: string
        - in: query
          name: labels
          description: Comma-separated labels that must be present on the thread.
          schema:
            type: string
        - in: query
          name: since
          description: Alias for lastMessageAfter. Returns threads with latest activity after this ISO timestamp.
          schema:
            type: string
            format: date-time
        - in: query
          name: lastMessageAfter
          schema:
            type: string
            format: date-time
        - in: query
          name: lastMessageBefore
          schema:
            type: string
            format: date-time
        - in: query
          name: limit
          schema:
            type: integer
            minimum: 1
            maximum: 250
        - in: query
          name: cursor
          description: Opaque cursor returned as nextCursor from a previous limited response.
          schema:
            type: string
      responses:
        "200":
          description: Customer conversation threads with optional nextCursor
          content:
            application/json:
              schema:
                type: object
                required: [data]
                properties:
                  data:
                    type: array
                    items:
                      type: object
                  nextCursor:
                    type: string
        "403":
          description: API key is not authorized for customer-wide threads
  /v1/customers/{customerId}/threads/{threadId}/messages:
    get:
      summary: List messages in one customer conversation thread
      description: Customer-scoped endpoint that returns paginated thread messages ordered oldest first. Inbox-scoped keys cannot access customer-wide thread transcripts.
      parameters:
        - in: path
          name: customerId
          required: true
          schema:
            type: string
        - in: path
          name: threadId
          required: true
          schema:
            type: string
        - in: query
          name: q
          description: Case-insensitive text search across sender, recipients, subject, plain text, and HTML.
          schema:
            type: string
        - in: query
          name: from
          description: Case-insensitive sender substring filter.
          schema:
            type: string
        - in: query
          name: to
          description: Case-insensitive recipient substring filter across to, cc, and bcc.
          schema:
            type: string
        - in: query
          name: subject
          description: Case-insensitive subject substring filter.
          schema:
            type: string
        - in: query
          name: direction
          schema:
            type: string
            enum: [inbound, outbound]
        - in: query
          name: status
          schema:
            type: string
        - in: query
          name: intent
          schema:
            type: string
        - in: query
          name: urgency
          schema:
            type: string
        - in: query
          name: workflowState
          schema:
            type: string
            enum: [open, claimed, resolved]
        - in: query
          name: since
          description: Alias for createdAfter. Returns messages created after this ISO timestamp.
          schema:
            type: string
            format: date-time
        - in: query
          name: createdBefore
          schema:
            type: string
            format: date-time
        - in: query
          name: limit
          schema:
            type: integer
            minimum: 1
            maximum: 250
        - in: query
          name: cursor
          description: Opaque cursor returned as nextCursor from a previous limited response.
          schema:
            type: string
      responses:
        "200":
          description: Customer thread messages with optional nextCursor
          content:
            application/json:
              schema:
                type: object
                required: [data]
                properties:
                  data:
                    type: array
                    items:
                      type: object
                  nextCursor:
                    type: string
        "403":
          description: API key is not authorized for customer-wide thread messages
    post:
      summary: Send a reply in one customer conversation thread
      description: Customer-scoped endpoint that sends a threaded reply from the owning inbox after resolving the thread internally. Inbox-scoped keys cannot access customer-wide thread replies.
      parameters:
        - in: path
          name: customerId
          required: true
          schema:
            type: string
        - in: path
          name: threadId
          required: true
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                to:
                  oneOf:
                    - type: string
                    - type: array
                      items:
                        type: string
                  description: Optional override. Defaults to the latest inbound sender in the thread.
                cc:
                  type: array
                  items:
                    type: string
                bcc:
                  type: array
                  items:
                    type: string
                subject:
                  type: string
                  description: Optional override. Defaults to a reply subject based on the latest thread message.
                text:
                  type: string
                html:
                  type: string
                attachments:
                  type: array
                  items:
                    type: object
                headers:
                  type: object
                  additionalProperties:
                    type: string
      responses:
        "202":
          description: Outbound reply accepted for delivery
        "403":
          description: API key is not authorized for customer-wide thread replies
        "404":
          description: Thread not found for this customer
  /v1/api-keys:
    get:
      summary: List API key metadata for a customer
      description: Returns key metadata only. Raw secrets and hashes are never included. Revoked keys are hidden unless includeRevoked=true.
      parameters:
        - in: query
          name: customerId
          required: true
          schema:
            type: string
        - in: query
          name: inboxId
          schema:
            type: string
        - in: query
          name: includeRevoked
          schema:
            type: boolean
      responses:
        "200":
          description: API key metadata
    post:
      summary: Create a customer-scoped or inbox-scoped API key
      description: Inbox-scoped keys must point to an inbox owned by the same customer. Raw secrets are returned once.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [customerId]
              properties:
                customerId:
                  type: string
                inboxId:
                  type: string
                name:
                  type: string
                scopes:
                  type: array
                  items:
                    type: string
                expiresAt:
                  type: string
      responses:
        "201":
          description: API key created. Raw secret is returned once.
  /v1/api-keys/{apiKeyId}/revoke:
    post:
      summary: Revoke an API key
      description: Revocation is idempotent and immediately prevents future authentication with the raw secret.
      parameters:
        - in: path
          name: apiKeyId
          required: true
          schema:
            type: string
      responses:
        "200":
          description: Revoked API key metadata
        "404":
          description: API key not found
  /v1/classification-rules:
    get:
      summary: List scoped inbound classification rules
      description: Returns active categorization rules by default. Rules can be scoped to a customer, deployment, or inbox and run before the built-in fallback classifier.
      parameters:
        - in: query
          name: customerId
          required: true
          schema:
            type: string
        - in: query
          name: deploymentId
          schema:
            type: string
        - in: query
          name: inboxId
          schema:
            type: string
        - in: query
          name: status
          schema:
            type: string
            enum: [active, paused]
        - in: query
          name: includeInactive
          schema:
            type: boolean
      responses:
        "200":
          description: Classification rule list
    post:
      summary: Create a scoped inbound classification rule
      description: Match simple sender, recipient, subject, body, or whole-message text and assign intent, urgency, labels, and route hints for agent routing.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [customerId, name, match, classification]
              properties:
                customerId:
                  type: string
                deploymentId:
                  type: string
                inboxId:
                  type: string
                name:
                  type: string
                priority:
                  type: integer
                  minimum: -1000
                  maximum: 1000
                match:
                  type: object
                  properties:
                    fromContains:
                      oneOf:
                        - type: string
                        - type: array
                          items:
                            type: string
                    toContains:
                      oneOf:
                        - type: string
                        - type: array
                          items:
                            type: string
                    subjectContains:
                      oneOf:
                        - type: string
                        - type: array
                          items:
                            type: string
                    bodyContains:
                      oneOf:
                        - type: string
                        - type: array
                          items:
                            type: string
                    anyContains:
                      oneOf:
                        - type: string
                        - type: array
                          items:
                            type: string
                classification:
                  type: object
                  properties:
                    intent:
                      type: string
                    urgency:
                      type: string
                    routeHint:
                      type: string
                    labels:
                      type: array
                      items:
                        type: string
      responses:
        "201":
          description: Classification rule created
  /v1/classification-rules/{ruleId}:
    patch:
      summary: Update or pause a classification rule
      parameters:
        - in: path
          name: ruleId
          required: true
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                name:
                  type: string
                status:
                  type: string
                  enum: [active, paused]
                priority:
                  type: integer
                match:
                  type: object
                classification:
                  type: object
                note:
                  type: string
      responses:
        "200":
          description: Updated classification rule
        "404":
          description: Classification rule not found
  /v1/provision-agent-email:
    post:
      summary: Idempotently provision a long-lived default inbox for a KriyAI deployed agent
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - customerExternalId
                - customerName
                - deploymentExternalId
                - deploymentName
                - agentExternalId
                - agentName
              properties:
                customerExternalId:
                  type: string
                customerName:
                  type: string
                deploymentExternalId:
                  type: string
                deploymentName:
                  type: string
                agentExternalId:
                  type: string
                agentName:
                  type: string
                agentRole:
                  type: string
                createRuntimeApiKey:
                  type: boolean
                  description: When true, returns a one-time inbox-scoped runtime API key for the provisioned agent.
      responses:
        "200":
          description: Provisioned customer, deployment, agent, inbox, email address, and optional runtime API key
  /v1/provision-deployment-agent-emails:
    post:
      summary: Idempotently provision long-lived inboxes for every agent in one deployment
      description: Batch provisioning endpoint for KriyAI swarm deployments. Retries reuse the same customer, deployment, agents, inboxes, and email addresses.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - customerExternalId
                - customerName
                - deploymentExternalId
                - deploymentName
                - agents
              properties:
                customerExternalId:
                  type: string
                customerName:
                  type: string
                deploymentExternalId:
                  type: string
                deploymentName:
                  type: string
                createRuntimeApiKeys:
                  type: boolean
                  description: When true, returns a one-time inbox-scoped runtime API key for each provisioned agent.
                agents:
                  type: array
                  minItems: 1
                  maxItems: 250
                  items:
                    type: object
                    required: [agentExternalId, agentName]
                    properties:
                      agentExternalId:
                        type: string
                      agentName:
                        type: string
                      agentRole:
                        type: string
                      createRuntimeApiKey:
                        type: boolean
                        description: Per-agent override to return a one-time inbox-scoped runtime API key for this agent.
      responses:
        "200":
          description: Provisioned customer, deployment, and per-agent inbox/email results with optional runtime API keys
  /v1/domains:
    get:
      summary: List customer domains
      parameters:
        - in: query
          name: customerId
          required: true
          schema:
            type: string
      responses:
        "200":
          description: Domains
    post:
      summary: Create a shared or custom sending/receiving domain
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [customerId, domain]
              properties:
                customerId:
                  type: string
                domain:
                  type: string
      responses:
        "201":
          description: Domain created with DNS records
  /v1/domains/{domainId}/verify:
    post:
      summary: Verify MX, SPF, DKIM, and DMARC DNS records for a domain
      parameters:
        - in: path
          name: domainId
          required: true
          schema:
            type: string
      responses:
        "200":
          description: Domain with latest DNS verification result
  /v1/domains/{domainId}/dns-records/refresh:
    post:
      summary: Rebuild MX, SPF, DKIM, and DMARC setup records from current mail configuration
      description: Use after rotating DKIM keys or changing the self-operated mail host. Custom domains return to pending until DNS is verified again.
      parameters:
        - in: path
          name: domainId
          required: true
          schema:
            type: string
      responses:
        "200":
          description: Domain with refreshed DNS records
  /v1/deployments:
    post:
      summary: Create a KriyAI deployment record
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [customerId, name]
              properties:
                customerId:
                  type: string
                name:
                  type: string
                externalId:
                  type: string
      responses:
        "201":
          description: Deployment created
  /v1/deployments/{deploymentId}/agents:
    get:
      summary: List agents in one deployment
      description: Customer-scoped endpoint that returns deployment agents with their long-lived default inbox ids and email addresses. Inbox-scoped keys cannot access deployment-wide rosters.
      parameters:
        - in: path
          name: deploymentId
          required: true
          schema:
            type: string
        - in: query
          name: q
          description: Case-insensitive search across agent id, external id, name, role, default email address, default inbox id, and deployment id.
          schema:
            type: string
        - in: query
          name: role
          schema:
            type: string
        - in: query
          name: externalId
          description: Exact external id filter.
          schema:
            type: string
        - in: query
          name: name
          description: Case-insensitive agent name substring filter.
          schema:
            type: string
        - in: query
          name: hasDefaultInbox
          schema:
            type: boolean
        - in: query
          name: since
          schema:
            type: string
            format: date-time
        - in: query
          name: createdAfter
          schema:
            type: string
            format: date-time
        - in: query
          name: createdBefore
          schema:
            type: string
            format: date-time
        - in: query
          name: limit
          schema:
            type: integer
            minimum: 1
            maximum: 250
        - in: query
          name: cursor
          schema:
            type: string
      responses:
        "200":
          description: Deployment agents with optional nextCursor
        "403":
          description: API key is not authorized for deployment-wide agents
  /v1/deployments/{deploymentId}/messages/claim-next:
    post:
      summary: Claim the next available deployment message
      description: Atomically picks the highest-priority available inbound message in the deployment, applies a workflow lease, and returns null when no work is available. Snoozed messages are skipped until their snoozedUntil timestamp is due. High-urgency messages are claimed before normal-urgency messages; ties use FIFO order. Threads with another active inbound claim are skipped so two workers do not handle the same conversation at once.
      parameters:
        - in: path
          name: deploymentId
          required: true
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [claimedBy]
              properties:
                claimedBy:
                  type: string
                leaseMs:
                  type: integer
                  minimum: 30000
                  maximum: 3600000
                filters:
                  type: object
                  properties:
                    intent:
                      type: string
                    urgency:
                      type: string
                    routeHint:
                      type: string
                    labels:
                      type: array
                      items:
                        type: string
      responses:
        "200":
          description: Claimed message or null
  /v1/deployments/{deploymentId}/messages/claim-next-batch:
    post:
      summary: Claim a bounded batch of deployment messages
      description: Claims up to limit available inbound messages from the deployment using the same ordering and safety rules as claim-next. The batch preserves assignment targeting, skips snoozed messages, and does not claim more than one active message from the same thread.
      parameters:
        - in: path
          name: deploymentId
          required: true
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [claimedBy]
              properties:
                claimedBy:
                  type: string
                leaseMs:
                  type: integer
                  minimum: 30000
                  maximum: 3600000
                limit:
                  type: integer
                  minimum: 1
                  maximum: 25
                  default: 10
                filters:
                  type: object
                  properties:
                    intent:
                      type: string
                    urgency:
                      type: string
                    routeHint:
                      type: string
                    labels:
                      type: array
                      items:
                        type: string
      responses:
        "200":
          description: Claimed message batch
  /v1/agents:
    post:
      summary: Create an agent identity
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [customerId, deploymentId, name]
              properties:
                customerId:
                  type: string
                deploymentId:
                  type: string
                name:
                  type: string
                externalId:
                  type: string
                role:
                  type: string
                defaultInboxUsername:
                  type: string
                  description: Optional custom local part for the agent default inbox. For example, support creates support@agents.noinfra.ai on the shared domain.
      responses:
        "201":
          description: Agent created
  /v1/inboxes:
    post:
      summary: Create an inbox for a deployed agent
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [customerId, deploymentId, agentId]
              properties:
                customerId:
                  type: string
                deploymentId:
                  type: string
                agentId:
                  type: string
                username:
                  type: string
                domain:
                  type: string
                displayName:
                  type: string
      responses:
        "201":
          description: Inbox created
  /v1/inboxes/{inboxId}:
    patch:
      summary: Update an agent inbox lifecycle state
      description: Set status to active or suspended. Suspended inboxes keep message history but reject inbound mail and block outbound sends.
      parameters:
        - in: path
          name: inboxId
          required: true
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                status:
                  type: string
                  enum: [active, suspended]
                displayName:
                  type: string
                note:
                  type: string
      responses:
        "200":
          description: Updated inbox
  /v1/inboxes/{inboxId}/drafts:
    get:
      summary: List inbox drafts
      description: Returns saved drafts for one agent inbox, newest updated first.
      parameters:
        - in: path
          name: inboxId
          required: true
          schema:
            type: string
        - in: query
          name: q
          schema:
            type: string
        - in: query
          name: status
          schema:
            type: string
            enum: [draft, scheduled, sent]
        - in: query
          name: label
          schema:
            type: string
        - in: query
          name: clientId
          schema:
            type: string
        - in: query
          name: limit
          schema:
            type: integer
            minimum: 1
            maximum: 250
        - in: query
          name: cursor
          schema:
            type: string
      responses:
        "200":
          description: Draft page
    post:
      summary: Create an inbox draft
      description: Saves an outbound message draft for review. clientId is idempotent per inbox.
      parameters:
        - in: path
          name: inboxId
          required: true
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                clientId:
                  type: string
                to:
                  oneOf:
                    - type: string
                    - type: array
                      items:
                        type: string
                cc:
                  type: array
                  items:
                    type: string
                bcc:
                  type: array
                  items:
                    type: string
                subject:
                  type: string
                text:
                  type: string
                html:
                  type: string
                labels:
                  type: array
                  items:
                    type: string
                sendAt:
                  type: string
                  format: date-time
      responses:
        "201":
          description: Draft created
  /v1/inboxes/{inboxId}/drafts/{draftId}:
    get:
      summary: Get one draft
      parameters:
        - in: path
          name: inboxId
          required: true
          schema:
            type: string
        - in: path
          name: draftId
          required: true
          schema:
            type: string
      responses:
        "200":
          description: Draft
    patch:
      summary: Update an unsent draft
      parameters:
        - in: path
          name: inboxId
          required: true
          schema:
            type: string
        - in: path
          name: draftId
          required: true
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
      responses:
        "200":
          description: Draft updated
    delete:
      summary: Delete an unsent draft
      parameters:
        - in: path
          name: inboxId
          required: true
          schema:
            type: string
        - in: path
          name: draftId
          required: true
          schema:
            type: string
      responses:
        "200":
          description: Draft deleted
  /v1/inboxes/{inboxId}/drafts/{draftId}/attachments/{attachmentId}:
    get:
      summary: Get draft attachment download metadata
      description: Returns draft attachment metadata and a download URL or data URL. The same route is also available through the /v0 compatibility alias.
      parameters:
        - in: path
          name: inboxId
          required: true
          schema:
            type: string
        - in: path
          name: draftId
          required: true
          schema:
            type: string
        - in: path
          name: attachmentId
          required: true
          schema:
            type: string
      responses:
        "200":
          description: Draft attachment download metadata
          content:
            application/json:
              schema:
                type: object
                required: [draft_id, attachment_id, size, download_url]
                properties:
                  draft_id:
                    type: string
                  attachment_id:
                    type: string
                  filename:
                    type: string
                  content_type:
                    type: string
                  size:
                    type: integer
                  download_url:
                    type: string
                  expires_at:
                    type: string
                    format: date-time
        "404":
          description: Draft or attachment not found
  /v1/inboxes/{inboxId}/drafts/{draftId}/send:
    post:
      summary: Send a saved draft
      description: Converts a draft into an outbound message and marks the draft sent.
      parameters:
        - in: path
          name: inboxId
          required: true
          schema:
            type: string
        - in: path
          name: draftId
          required: true
          schema:
            type: string
      responses:
        "202":
          description: Draft sent or queued
  /v1/drafts/send-due:
    post:
      summary: Process due scheduled drafts
      description: Sends scheduled drafts whose sendAt timestamp is due. The background worker calls the same processor automatically.
      parameters:
        - in: query
          name: customerId
          schema:
            type: string
        - in: query
          name: limit
          schema:
            type: integer
            minimum: 1
            maximum: 250
      responses:
        "200":
          description: Due drafts processed
  /v1/inboxes/{inboxId}/messages:
    get:
      summary: List inbox messages
      description: Returns inbox messages newest first. Inbound reply objects include extractedText and extractedHtml fields with quoted history stripped when possible; fall back to text or html when no extraction is available.
      parameters:
        - in: path
          name: inboxId
          required: true
          schema:
            type: string
        - in: query
          name: q
          description: Case-insensitive text search across sender, recipients, subject, plain text, and HTML.
          schema:
            type: string
        - in: query
          name: from
          description: Case-insensitive sender substring filter.
          schema:
            type: string
        - in: query
          name: to
          description: Case-insensitive recipient substring filter across to, cc, and bcc.
          schema:
            type: string
        - in: query
          name: subject
          description: Case-insensitive subject substring filter.
          schema:
            type: string
        - in: query
          name: direction
          schema:
            type: string
            enum: [inbound, outbound]
        - in: query
          name: status
          schema:
            type: string
        - in: query
          name: intent
          schema:
            type: string
        - in: query
          name: urgency
          schema:
            type: string
        - in: query
          name: routeHint
          schema:
            type: string
        - in: query
          name: label
          description: Classification label that must be present. Repeat to require multiple labels.
          schema:
            type: string
        - in: query
          name: needsHumanReview
          schema:
            type: boolean
        - in: query
          name: workflowState
          schema:
            type: string
            enum: [open, claimed, resolved]
        - in: query
          name: assignedTo
          schema:
            type: string
        - in: query
          name: snoozed
          schema:
            type: boolean
        - in: query
          name: snoozedBy
          schema:
            type: string
        - in: query
          name: claimedBy
          schema:
            type: string
        - in: query
          name: resolvedBy
          schema:
            type: string
        - in: query
          name: reopenedBy
          schema:
            type: string
        - in: query
          name: availableForClaim
          schema:
            type: boolean
        - in: query
          name: since
          description: Alias for createdAfter. Returns messages created after this ISO timestamp.
          schema:
            type: string
            format: date-time
        - in: query
          name: createdAfter
          schema:
            type: string
            format: date-time
        - in: query
          name: createdBefore
          schema:
            type: string
            format: date-time
        - in: query
          name: limit
          schema:
            type: integer
            minimum: 1
            maximum: 250
        - in: query
          name: cursor
          description: Opaque cursor returned as nextCursor from a previous limited response.
          schema:
            type: string
      responses:
        "200":
          description: Messages
          content:
            application/json:
              schema:
                type: object
                required: [data]
                properties:
                  data:
                    type: array
                    items:
                      type: object
                  nextCursor:
                    type: string
    post:
      summary: Send a message from an agent inbox
      parameters:
        - in: path
          name: inboxId
          required: true
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [to, subject]
              properties:
                to:
                  oneOf:
                    - type: string
                    - type: array
                      items:
                        type: string
                cc:
                  type: array
                  items:
                    type: string
                bcc:
                  type: array
                  items:
                    type: string
                subject:
                  type: string
                text:
                  type: string
                html:
                  type: string
                attachments:
                  type: array
                  description: Optional outbound attachments sent as MIME parts by raw Direct-MX delivery. Up to 10 attachments and 10 MiB total decoded content per message.
                  maxItems: 10
                  items:
                    type: object
                    required: [filename, contentBase64]
                    properties:
                      filename:
                        type: string
                      contentType:
                        type: string
                        default: application/octet-stream
                      contentId:
                        type: string
                      size:
                        type: integer
                        description: Decoded byte size. The service computes this from contentBase64 when storing.
                      contentBase64:
                        type: string
                threadId:
                  type: string
                idempotencyKey:
                  type: string
                  description: Same key on the same inbox returns the original outbound message instead of sending twice.
                labels:
                  type: array
                  items:
                    type: string
      responses:
        "202":
          description: Message accepted
        "409":
          description: Message blocked by policy
  /v1/inboxes/{inboxId}/messages/search:
    get:
      summary: Search inbox messages
      description: AgentMail-style full-text search route. The same route is also available through the /v0 compatibility alias.
      parameters:
        - in: path
          name: inboxId
          required: true
          schema:
            type: string
        - in: query
          name: q
          required: true
          description: Search text matched against sender, recipients, subject, plain text, and HTML.
          schema:
            type: string
        - in: query
          name: limit
          schema:
            type: integer
            minimum: 1
            maximum: 100
        - in: query
          name: page_token
          description: Opaque cursor returned as next_page_token from a previous limited response.
          schema:
            type: string
      responses:
        "200":
          description: Search results
          content:
            application/json:
              schema:
                type: object
                required: [limit, count, messages]
                properties:
                  limit:
                    type: integer
                  count:
                    type: integer
                  next_page_token:
                    type: string
                  messages:
                    type: array
                    items:
                      type: object
  /v1/inboxes/{inboxId}/messages/batch-get:
    post:
      summary: Batch get inbox messages
      description: Returns up to 500 messages by id, omitting message bodies to match AgentMail-style list metadata. The same route is also available through the /v0 compatibility alias.
      parameters:
        - in: path
          name: inboxId
          required: true
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [message_ids]
              properties:
                message_ids:
                  type: array
                  maxItems: 500
                  items:
                    type: string
      responses:
        "200":
          description: Batch message metadata
          content:
            application/json:
              schema:
                type: object
                required: [limit, count, messages]
                properties:
                  limit:
                    type: integer
                  count:
                    type: integer
                  messages:
                    type: array
                    items:
                      type: object
  /v1/inboxes/{inboxId}/messages/batch-update:
    post:
      summary: Batch update message labels
      description: Adds and removes labels for up to 500 inbox messages. The same route is also available through the /v0 compatibility alias.
      parameters:
        - in: path
          name: inboxId
          required: true
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [message_ids]
              properties:
                message_ids:
                  type: array
                  maxItems: 500
                  items:
                    type: string
                add_labels:
                  type: array
                  items:
                    type: string
                remove_labels:
                  type: array
                  items:
                    type: string
                addLabels:
                  type: array
                  items:
                    type: string
                removeLabels:
                  type: array
                  items:
                    type: string
      responses:
        "200":
          description: Updated message labels
          content:
            application/json:
              schema:
                type: object
                required: [limit, count, messages]
                properties:
                  limit:
                    type: integer
                  count:
                    type: integer
                  messages:
                    type: array
                    items:
                      type: object
  /v1/inboxes/{inboxId}/messages/{messageId}:
    get:
      summary: Get one inbox message
      description: Returns a single message after enforcing customer and inbox boundaries. Inbound replies include extractedText and extractedHtml fields with quoted history stripped when possible; fall back to text or html when no extraction is available.
      parameters:
        - in: path
          name: inboxId
          required: true
          schema:
            type: string
        - in: path
          name: messageId
          required: true
          schema:
            type: string
      responses:
        "200":
          description: Message
        "403":
          description: Missing messages:read scope or inbox access
        "404":
          description: Message not found in the inbox
    patch:
      summary: Add or remove message labels
      description: Updates the message label set while preserving other classification fields.
      parameters:
        - in: path
          name: inboxId
          required: true
          schema:
            type: string
        - in: path
          name: messageId
          required: true
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                addLabels:
                  type: array
                  items:
                    type: string
                removeLabels:
                  type: array
                  items:
                    type: string
      responses:
        "200":
          description: Updated message
    delete:
      summary: Delete one inbox message
      description: Permanently deletes a message from the inbox and updates thread state. The same route is also available through the /v0 compatibility alias.
      parameters:
        - in: path
          name: inboxId
          required: true
          schema:
            type: string
        - in: path
          name: messageId
          required: true
          schema:
            type: string
      responses:
        "204":
          description: Message deleted
        "403":
          description: Missing messages:classify scope or inbox access
        "404":
          description: Message not found in the inbox
  /v1/inboxes/{inboxId}/messages/{messageId}/attachments/{attachmentId}:
    get:
      summary: Get message attachment download metadata
      description: Returns attachment metadata and a download URL or data URL for agent consumption. The same route is also available through the /v0 compatibility alias.
      parameters:
        - in: path
          name: inboxId
          required: true
          schema:
            type: string
        - in: path
          name: messageId
          required: true
          schema:
            type: string
        - in: path
          name: attachmentId
          required: true
          schema:
            type: string
      responses:
        "200":
          description: Attachment download metadata
          content:
            application/json:
              schema:
                type: object
                required: [attachment_id, size, download_url]
                properties:
                  attachment_id:
                    type: string
                  filename:
                    type: string
                  content_type:
                    type: string
                  size:
                    type: integer
                  download_url:
                    type: string
                  expires_at:
                    type: string
                    format: date-time
        "404":
          description: Message or attachment not found
  /v1/inboxes/{inboxId}/messages/{messageId}/raw:
    get:
      summary: Get raw message download metadata
      description: Returns raw RFC822-style message metadata and a download URL or data URL. The same route is also available through the /v0 compatibility alias.
      parameters:
        - in: path
          name: inboxId
          required: true
          schema:
            type: string
        - in: path
          name: messageId
          required: true
          schema:
            type: string
      responses:
        "200":
          description: Raw message download metadata
          content:
            application/json:
              schema:
                type: object
                required: [message_id, size, download_url]
                properties:
                  message_id:
                    type: string
                  size:
                    type: integer
                  download_url:
                    type: string
                  expires_at:
                    type: string
                    format: date-time
        "404":
          description: Message not found in the inbox
  /v1/inboxes/{inboxId}/messages/{messageId}/reply:
    post:
      summary: Reply to one message
      description: Sends an outbound threaded reply. Defaults to the original sender when to is omitted.
      parameters:
        - in: path
          name: inboxId
          required: true
          schema:
            type: string
        - in: path
          name: messageId
          required: true
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
      responses:
        "202":
          description: Reply accepted
  /v1/inboxes/{inboxId}/messages/{messageId}/forward:
    post:
      summary: Forward one message
      parameters:
        - in: path
          name: inboxId
          required: true
          schema:
            type: string
        - in: path
          name: messageId
          required: true
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [to]
              properties:
                to:
                  type: array
                  items:
                    type: string
      responses:
        "202":
          description: Forward accepted
  /v1/inboxes/{inboxId}/messages/claim-next:
    post:
      summary: Claim the next available inbox message
      description: Atomically picks the highest-priority available inbound message in one inbox, applies a workflow lease, and returns null when no work is available. Snoozed messages are skipped until their snoozedUntil timestamp is due. High-urgency messages are claimed before normal-urgency messages; ties use FIFO order. Threads with another active inbound claim are skipped so two workers do not handle the same conversation at once.
      parameters:
        - in: path
          name: inboxId
          required: true
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [claimedBy]
              properties:
                claimedBy:
                  type: string
                leaseMs:
                  type: integer
                  minimum: 30000
                  maximum: 3600000
                filters:
                  type: object
                  properties:
                    intent:
                      type: string
                    urgency:
                      type: string
                    routeHint:
                      type: string
                    labels:
                      type: array
                      items:
                        type: string
      responses:
        "200":
          description: Claimed message or null
  /v1/inboxes/{inboxId}/messages/claim-next-batch:
    post:
      summary: Claim a bounded batch of inbox messages
      description: Claims up to limit available inbound messages from one inbox using the same ordering and safety rules as claim-next. The batch preserves assignment targeting, skips snoozed messages, and does not claim more than one active message from the same thread.
      parameters:
        - in: path
          name: inboxId
          required: true
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [claimedBy]
              properties:
                claimedBy:
                  type: string
                leaseMs:
                  type: integer
                  minimum: 30000
                  maximum: 3600000
                limit:
                  type: integer
                  minimum: 1
                  maximum: 25
                  default: 10
                filters:
                  type: object
                  properties:
                    intent:
                      type: string
                    urgency:
                      type: string
                    routeHint:
                      type: string
                    labels:
                      type: array
                      items:
                        type: string
      responses:
        "200":
          description: Claimed message batch
  /v1/inboxes/{inboxId}/messages/{messageId}/classification:
    patch:
      summary: Update a message classification and routing hint
      description: Agent-facing endpoint for correcting intent, urgency, labels, and route hints after inbound classification.
      parameters:
        - in: path
          name: inboxId
          required: true
          schema:
            type: string
        - in: path
          name: messageId
          required: true
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                intent:
                  type: string
                  examples: [billing, support, general]
                urgency:
                  type: string
                  examples: [low, normal, high]
                routeHint:
                  type: string
                  examples: [agent, human_review]
                labels:
                  type: array
                  items:
                    type: string
                note:
                  type: string
                  description: Optional audit note explaining the agent override.
      responses:
        "200":
          description: Message with updated classification
        "403":
          description: Missing messages:classify scope or inbox access
        "404":
          description: Message not found in the inbox
  /v1/inboxes/{inboxId}/messages/{messageId}/cancel:
    post:
      summary: Cancel a queued outbound message
      description: Stops a queued or retry-scheduled outbound message before the async delivery worker sends it.
      parameters:
        - in: path
          name: inboxId
          required: true
          schema:
            type: string
        - in: path
          name: messageId
          required: true
          schema:
            type: string
      requestBody:
        required: false
        content:
          application/json:
            schema:
              type: object
              properties:
                canceledBy:
                  type: string
                reason:
                  type: string
                note:
                  type: string
      responses:
        "200":
          description: Outbound message marked canceled
        "403":
          description: Missing messages:send scope or inbox access
        "404":
          description: Message not found in the inbox
        "409":
          description: Message has already been sent, delivered, blocked, canceled, or is inbound-only
  /v1/inboxes/{inboxId}/messages/{messageId}/claim:
    post:
      summary: Claim an inbound message for one agent worker
      description: Creates or renews a bounded workflow lease so multiple workers do not handle the same message concurrently. A message with a future snoozedUntil cannot be claimed until it is due or unsnoozed.
      parameters:
        - in: path
          name: inboxId
          required: true
          schema:
            type: string
        - in: path
          name: messageId
          required: true
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [claimedBy]
              properties:
                claimedBy:
                  type: string
                leaseMs:
                  type: integer
                  minimum: 30000
                  maximum: 3600000
      responses:
        "200":
          description: Message with claimed workflow state
        "409":
          description: Message or thread is already claimed by another worker
  /v1/inboxes/{inboxId}/messages/{messageId}/assign:
    post:
      summary: Assign a message to a specific agent runtime queue
      description: Assignment is stored in message workflow state. Assigned messages remain visible in monitoring, but claim-next only returns them to the assigned worker while unassigned messages stay shared.
      parameters:
        - in: path
          name: inboxId
          required: true
          schema:
            type: string
        - in: path
          name: messageId
          required: true
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [assignedTo, assignedBy]
              properties:
                assignedTo:
                  type: string
                  description: Runtime, agent, or queue name that should claim the message.
                assignedBy:
                  type: string
                reason:
                  type: string
                note:
                  type: string
      responses:
        "200":
          description: Message with updated workflow assignment
        "403":
          description: Missing messages:claim scope or inbox access
        "404":
          description: Message not found in the inbox
        "409":
          description: Message is actively claimed by another worker
  /v1/inboxes/{inboxId}/messages/{messageId}/unassign:
    post:
      summary: Return an assigned message to the shared queue
      description: Removes assignment metadata from message workflow state while preserving notes and other workflow context. The message becomes claimable by any eligible worker unless another workflow state blocks it.
      parameters:
        - in: path
          name: inboxId
          required: true
          schema:
            type: string
        - in: path
          name: messageId
          required: true
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [unassignedBy]
              properties:
                unassignedBy:
                  type: string
                reason:
                  type: string
                note:
                  type: string
      responses:
        "200":
          description: Message with assignment metadata removed
        "403":
          description: Missing messages:claim scope or inbox access
        "404":
          description: Message not found in the inbox
        "409":
          description: Message is actively claimed by another worker
  /v1/inboxes/{inboxId}/messages/{messageId}/snooze:
    post:
      summary: Defer a message until a future time
      description: Stores snooze metadata in message workflow state, preserves assignment and notes, emits message.snoozed, and removes the message from claim-next until snoozedUntil is due.
      parameters:
        - in: path
          name: inboxId
          required: true
          schema:
            type: string
        - in: path
          name: messageId
          required: true
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [snoozedBy, until]
              properties:
                snoozedBy:
                  type: string
                until:
                  type: string
                  format: date-time
                reason:
                  type: string
                note:
                  type: string
      responses:
        "200":
          description: Message with snoozed workflow state
        "403":
          description: Missing messages:claim scope or inbox access
        "404":
          description: Message not found in the inbox
        "409":
          description: Message is actively claimed by another worker
  /v1/inboxes/{inboxId}/messages/{messageId}/unsnooze:
    post:
      summary: Wake a snoozed message
      description: Clears active snooze metadata, preserves assignment and notes, emits message.unsnoozed, and returns the message to the open claim queue.
      parameters:
        - in: path
          name: inboxId
          required: true
          schema:
            type: string
        - in: path
          name: messageId
          required: true
          schema:
            type: string
      requestBody:
        required: false
        content:
          application/json:
            schema:
              type: object
              properties:
                unsnoozedBy:
                  type: string
                actor:
                  type: string
                reason:
                  type: string
                note:
                  type: string
      responses:
        "200":
          description: Message returned to open workflow state
        "403":
          description: Missing messages:claim scope or inbox access
        "404":
          description: Message not found in the inbox
        "409":
          description: Message is actively claimed by another worker
  /v1/inboxes/{inboxId}/messages/{messageId}/notes:
    get:
      summary: List durable agent handoff notes for a message
      description: Notes are stored inside message workflow state and are useful for agent handoff, supervision, and debugging.
      parameters:
        - in: path
          name: inboxId
          required: true
          schema:
            type: string
        - in: path
          name: messageId
          required: true
          schema:
            type: string
      responses:
        "200":
          description: Message notes ordered oldest first
        "403":
          description: Missing messages:read scope or inbox access
        "404":
          description: Message not found in the inbox
    post:
      summary: Add a durable agent handoff note to a message
      description: Appends a bounded note to message workflow state, emits message.note_created, and writes an audit log.
      parameters:
        - in: path
          name: inboxId
          required: true
          schema:
            type: string
        - in: path
          name: messageId
          required: true
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [author, text]
              properties:
                author:
                  type: string
                text:
                  type: string
                  maxLength: 4000
      responses:
        "201":
          description: Message with appended workflow note
        "403":
          description: Missing messages:claim scope or inbox access
        "404":
          description: Message not found in the inbox
  /v1/inboxes/{inboxId}/messages/{messageId}/resolve:
    post:
      summary: Mark a claimed message as resolved
      description: Completes the agent workflow for a message after the worker has handled it.
      parameters:
        - in: path
          name: inboxId
          required: true
          schema:
            type: string
        - in: path
          name: messageId
          required: true
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [resolvedBy]
              properties:
                resolvedBy:
                  type: string
                resolution:
                  type: string
                note:
                  type: string
      responses:
        "200":
          description: Message with resolved workflow state
        "409":
          description: Message is actively claimed by another worker
  /v1/inboxes/{inboxId}/messages/{messageId}/reopen:
    post:
      summary: Reopen a resolved message
      description: Returns a handled message to the open workflow queue when more agent work is needed.
      parameters:
        - in: path
          name: inboxId
          required: true
          schema:
            type: string
        - in: path
          name: messageId
          required: true
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [reopenedBy]
              properties:
                reopenedBy:
                  type: string
                reason:
                  type: string
                note:
                  type: string
      responses:
        "200":
          description: Message returned to open workflow state
        "409":
          description: Message is actively claimed by another worker
  /v1/inboxes/{inboxId}/threads:
    get:
      summary: List inbox threads
      description: Results are ordered by newest thread activity first. Use query, subject, label, limit, and cursor filters to page through long-lived inbox history.
      parameters:
        - in: path
          name: inboxId
          required: true
          schema:
            type: string
        - in: query
          name: q
          description: Case-insensitive search across thread id and subject.
          schema:
            type: string
        - in: query
          name: subject
          description: Case-insensitive subject substring filter.
          schema:
            type: string
        - in: query
          name: label
          description: Label that must be present on the thread. Repeat to require multiple labels.
          schema:
            type: string
        - in: query
          name: labels
          description: Comma-separated labels that must be present on the thread.
          schema:
            type: string
        - in: query
          name: since
          description: Alias for lastMessageAfter.
          schema:
            type: string
            format: date-time
        - in: query
          name: lastMessageAfter
          schema:
            type: string
            format: date-time
        - in: query
          name: lastMessageBefore
          schema:
            type: string
            format: date-time
        - in: query
          name: limit
          schema:
            type: integer
            minimum: 1
            maximum: 250
        - in: query
          name: cursor
          schema:
            type: string
      responses:
        "200":
          description: Threads with optional nextCursor
  /v1/inboxes/{inboxId}/threads/{threadId}:
    get:
      summary: Get one inbox thread
      description: Returns a single thread, including its current labels.
      parameters:
        - in: path
          name: inboxId
          required: true
          schema:
            type: string
        - in: path
          name: threadId
          required: true
          schema:
            type: string
      responses:
        "200":
          description: Thread
        "404":
          description: Thread not found in the inbox
    patch:
      summary: Add or remove thread labels
      description: Updates the thread label set while preserving message and draft labels.
      parameters:
        - in: path
          name: inboxId
          required: true
          schema:
            type: string
        - in: path
          name: threadId
          required: true
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                addLabels:
                  type: array
                  items:
                    type: string
                removeLabels:
                  type: array
                  items:
                    type: string
                note:
                  type: string
      responses:
        "200":
          description: Thread with updated labels
        "404":
          description: Thread not found in the inbox
  /v1/inboxes/{inboxId}/threads/{threadId}/attachments/{attachmentId}:
    get:
      summary: Get thread attachment download metadata
      description: Finds an attachment within any message in the thread and returns download metadata. The same route is also available through the /v0 compatibility alias.
      parameters:
        - in: path
          name: inboxId
          required: true
          schema:
            type: string
        - in: path
          name: threadId
          required: true
          schema:
            type: string
        - in: path
          name: attachmentId
          required: true
          schema:
            type: string
      responses:
        "200":
          description: Thread attachment download metadata
          content:
            application/json:
              schema:
                type: object
                required: [thread_id, message_id, attachment_id, size, download_url]
                properties:
                  thread_id:
                    type: string
                  message_id:
                    type: string
                  attachment_id:
                    type: string
                  filename:
                    type: string
                  content_type:
                    type: string
                  size:
                    type: integer
                  download_url:
                    type: string
                  expires_at:
                    type: string
                    format: date-time
        "404":
          description: Thread or attachment not found
  /v1/inboxes/{inboxId}/threads/{threadId}/messages:
    get:
      summary: List messages in one inbox thread
      description: Results are ordered oldest first. Use limit and cursor to page through long conversations.
      parameters:
        - in: path
          name: inboxId
          required: true
          schema:
            type: string
        - in: path
          name: threadId
          required: true
          schema:
            type: string
        - in: query
          name: since
          schema:
            type: string
            format: date-time
        - in: query
          name: createdBefore
          schema:
            type: string
            format: date-time
        - in: query
          name: limit
          schema:
            type: integer
            minimum: 1
            maximum: 250
        - in: query
          name: cursor
          schema:
            type: string
      responses:
        "200":
          description: Thread messages ordered oldest first with optional nextCursor
    post:
      summary: Send an outbound reply in one inbox thread
      description: Sends using the thread id and automatically sets email reply headers from known thread Message-IDs.
      parameters:
        - in: path
          name: inboxId
          required: true
          schema:
            type: string
        - in: path
          name: threadId
          required: true
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                to:
                  oneOf:
                    - type: string
                    - type: array
                      items:
                        type: string
                cc:
                  type: array
                  items:
                    type: string
                bcc:
                  type: array
                  items:
                    type: string
                subject:
                  type: string
                text:
                  type: string
                html:
                  type: string
                idempotencyKey:
                  type: string
      responses:
        "202":
          description: Reply accepted, queued, or sent
  /v1/inboxes/{inboxId}/activity:
    get:
      summary: Get monitoring counters and recent activity for an inbox
      description: Returns message counters, freshness timestamps including lastMessageAt, lastInboundAt, and lastOutboundAt, workflow queue counters including availableForClaim, activeClaims, expiredClaims, and oldestAvailableAt, plus recent messages for one inbox.
      parameters:
        - in: path
          name: inboxId
          required: true
          schema:
            type: string
      responses:
        "200":
          description: Inbox activity summary
  /v1/deployments/{deploymentId}/messages:
    get:
      summary: List deployment messages across an agent swarm
      description: Customer-scoped endpoint that returns a filtered cross-inbox work queue for one deployment. Inbox-scoped keys cannot access deployment-wide messages.
      parameters:
        - in: path
          name: deploymentId
          required: true
          schema:
            type: string
        - in: query
          name: q
          description: Case-insensitive text search across sender, recipients, subject, plain text, and HTML.
          schema:
            type: string
        - in: query
          name: from
          description: Case-insensitive sender substring filter.
          schema:
            type: string
        - in: query
          name: to
          description: Case-insensitive recipient substring filter across to, cc, and bcc.
          schema:
            type: string
        - in: query
          name: subject
          description: Case-insensitive subject substring filter.
          schema:
            type: string
        - in: query
          name: direction
          schema:
            type: string
            enum: [inbound, outbound]
        - in: query
          name: status
          schema:
            type: string
        - in: query
          name: intent
          schema:
            type: string
        - in: query
          name: urgency
          schema:
            type: string
        - in: query
          name: routeHint
          schema:
            type: string
        - in: query
          name: label
          description: Classification label that must be present. Repeat to require multiple labels.
          schema:
            type: string
        - in: query
          name: needsHumanReview
          schema:
            type: boolean
        - in: query
          name: workflowState
          schema:
            type: string
            enum: [open, claimed, resolved]
        - in: query
          name: assignedTo
          schema:
            type: string
        - in: query
          name: snoozed
          schema:
            type: boolean
        - in: query
          name: snoozedBy
          schema:
            type: string
        - in: query
          name: claimedBy
          schema:
            type: string
        - in: query
          name: resolvedBy
          schema:
            type: string
        - in: query
          name: reopenedBy
          schema:
            type: string
        - in: query
          name: availableForClaim
          schema:
            type: boolean
        - in: query
          name: since
          description: Alias for createdAfter. Returns messages created after this ISO timestamp.
          schema:
            type: string
            format: date-time
        - in: query
          name: createdAfter
          schema:
            type: string
            format: date-time
        - in: query
          name: createdBefore
          schema:
            type: string
            format: date-time
        - in: query
          name: limit
          schema:
            type: integer
            minimum: 1
            maximum: 250
        - in: query
          name: cursor
          description: Opaque cursor returned as nextCursor from a previous limited response.
          schema:
            type: string
      responses:
        "200":
          description: Filtered messages across deployment inboxes
          content:
            application/json:
              schema:
                type: object
                required: [data]
                properties:
                  data:
                    type: array
                    items:
                      type: object
                  nextCursor:
                    type: string
        "403":
          description: API key is not authorized for deployment-wide messages
        "404":
          description: Deployment not found
  /v1/deployments/{deploymentId}/activity:
    get:
      summary: Get monitoring counters and recent activity for a deployed agent swarm
      description: Customer-scoped endpoint that rolls up message counters, freshness timestamps including lastMessageAt, lastInboundAt, and lastOutboundAt, workflow queue counters including availableForClaim, activeClaims, expiredClaims, and oldestAvailableAt, full roster entries for every agent including zero-message agents, and recent messages across all inboxes in one deployment. Inbox-scoped keys cannot access deployment-wide activity.
      parameters:
        - in: path
          name: deploymentId
          required: true
          schema:
            type: string
      responses:
        "200":
          description: Deployment activity summary with aggregate counters, per-agent counters, and recent messages
        "403":
          description: API key is not authorized for deployment-wide activity
        "404":
          description: Deployment not found
  /v1/provider/recipients/{address}:
    get:
      summary: Validate an inbound SMTP recipient before accepting message data
      description: Root-only endpoint used by the self-operated SMTP edge during RCPT TO.
      parameters:
        - in: path
          name: address
          required: true
          schema:
            type: string
      responses:
        "200":
          description: Recipient is an active inbox
        "404":
          description: Recipient is not an active inbox
  /v1/provider/inbound:
    post:
      summary: Local/provider inbound webhook adapter
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [from, to, subject]
              properties:
                from:
                  type: string
                to:
                  type: string
                subject:
                  type: string
                text:
                  type: string
                html:
                  type: string
                headers:
                  type: object
                rawRef:
                  type: string
                attachments:
                  type: array
                  description: Optional inbound attachments from the self-operated SMTP edge. Up to 10 attachments and 10 MiB total decoded content per message.
                  maxItems: 10
                  items:
                    type: object
                    required: [filename, contentBase64]
                    properties:
                      filename:
                        type: string
                      contentType:
                        type: string
                        default: application/octet-stream
                      contentDisposition:
                        type: string
                        enum: [attachment, inline]
                      contentId:
                        type: string
                      size:
                        type: integer
                        description: Decoded byte size. The service computes this from contentBase64 when storing.
                      contentBase64:
                        type: string
      responses:
        "202":
          description: Inbound message accepted
  /v1/provider/mailgun/inbound:
    post:
      summary: Mailgun inbound route endpoint
      security: []
      requestBody:
        required: true
        content:
          application/x-www-form-urlencoded:
            schema:
              type: object
              required: [signature, timestamp, token, recipient, sender, subject]
              properties:
                signature:
                  type: string
                timestamp:
                  type: string
                token:
                  type: string
                recipient:
                  type: string
                sender:
                  type: string
                from:
                  type: string
                subject:
                  type: string
                body-plain:
                  type: string
                stripped-text:
                  type: string
                body-html:
                  type: string
                stripped-html:
                  type: string
                message-headers:
                  type: string
      responses:
        "200":
          description: Inbound message accepted
        "406":
          description: Signature rejected
  /v1/provider/mailgun/events:
    post:
      summary: Mailgun delivery event webhook endpoint
      security: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [signature, event-data]
              properties:
                signature:
                  type: object
                  required: [timestamp, token, signature]
                  properties:
                    timestamp:
                      type: string
                    token:
                      type: string
                    signature:
                      type: string
                event-data:
                  type: object
                  properties:
                    event:
                      type: string
                    id:
                      type: string
                    recipient:
                      type: string
                    severity:
                      type: string
                    reason:
                      type: string
      responses:
        "200":
          description: Delivery event accepted and message status updated
        "406":
          description: Signature rejected
  /v1/webhooks:
    get:
      summary: List customer webhooks
      parameters:
        - in: query
          name: customerId
          required: true
          schema:
            type: string
      responses:
        "200":
          description: Webhooks
    post:
      summary: Create a signed webhook subscription
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [customerId, url]
              properties:
                customerId:
                  type: string
                url:
                  type: string
                eventTypes:
                  type: array
                  description: Defaults to message.received, message.sent, message.failed, message.blocked, message.canceled, message.assigned, message.unassigned, message.snoozed, message.unsnoozed, message.note_created, and suppression.created.
                  items:
                    type: string
      responses:
        "201":
          description: Webhook created
  /v1/webhooks/{webhookId}:
    patch:
      summary: Update a signed webhook subscription
      description: Pause, reactivate, retarget, or change event subscriptions for a customer routing webhook.
      parameters:
        - in: path
          name: webhookId
          required: true
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                url:
                  type: string
                eventTypes:
                  type: array
                  items:
                    type: string
                status:
                  type: string
                  enum: [active, paused]
                note:
                  type: string
      responses:
        "200":
          description: Webhook updated
        "404":
          description: Webhook not found
  /v1/webhooks/delivery-preview:
    get:
      summary: Preview signed webhook delivery payloads for a customer
      parameters:
        - in: query
          name: customerId
          required: true
          schema:
            type: string
        - in: query
          name: eventType
          schema:
            type: string
      responses:
        "200":
          description: Signed delivery previews
  /v1/webhook-deliveries:
    get:
      summary: List signed webhook delivery attempts
      description: Results are ordered newest first. Use limit and cursor to page through long-lived delivery history.
      parameters:
        - in: query
          name: customerId
          schema:
            type: string
        - in: query
          name: webhookId
          schema:
            type: string
        - in: query
          name: eventId
          schema:
            type: string
        - in: query
          name: status
          schema:
            type: string
            enum: [pending, delivered, failed, exhausted]
        - in: query
          name: since
          schema:
            type: string
            format: date-time
        - in: query
          name: createdBefore
          schema:
            type: string
            format: date-time
        - in: query
          name: limit
          schema:
            type: integer
            minimum: 1
            maximum: 250
        - in: query
          name: cursor
          schema:
            type: string
      responses:
        "200":
          description: Webhook delivery attempts with optional nextCursor
  /v1/webhook-deliveries/retry-due:
    post:
      summary: Retry due failed webhook delivery attempts
      parameters:
        - in: query
          name: customerId
          schema:
            type: string
        - in: query
          name: limit
          schema:
            type: integer
            default: 25
      responses:
        "200":
          description: Retried webhook delivery attempts
  /v1/outbound-deliveries/retry-due:
    post:
      summary: Retry due failed outbound email deliveries
      parameters:
        - in: query
          name: customerId
          schema:
            type: string
        - in: query
          name: limit
          schema:
            type: integer
            default: 25
      responses:
        "200":
          description: Retried outbound messages
  /v1/outbound-deliveries/failures:
    get:
      summary: List failed outbound email deliveries
      description: Returns failed outbound messages for operator triage. By default, pass acknowledged=false to inspect failures that still need attention.
      parameters:
        - in: query
          name: customerId
          schema:
            type: string
        - in: query
          name: acknowledged
          schema:
            type: boolean
        - in: query
          name: since
          schema:
            type: string
            format: date-time
        - in: query
          name: createdBefore
          schema:
            type: string
            format: date-time
        - in: query
          name: limit
          schema:
            type: integer
            minimum: 1
            maximum: 250
        - in: query
          name: cursor
          schema:
            type: string
      responses:
        "200":
          description: Failed outbound messages with optional nextCursor
  /v1/outbound-deliveries/{messageId}/acknowledge:
    post:
      summary: Acknowledge an exhausted failed outbound delivery
      description: Marks a failed outbound message without a scheduled retry as operator-acknowledged without deleting the message history.
      parameters:
        - in: path
          name: messageId
          required: true
          schema:
            type: string
      requestBody:
        required: false
        content:
          application/json:
            schema:
              type: object
              properties:
                acknowledgedBy:
                  type: string
                reason:
                  type: string
      responses:
        "200":
          description: Acknowledged outbound message
  /v1/outbound-deliveries/summary:
    get:
      summary: Inspect outbound email delivery backlog
      description: Returns queue, retry, and terminal delivery counters for raw outbound operations.
      parameters:
        - in: query
          name: customerId
          schema:
            type: string
      responses:
        "200":
          description: Outbound delivery backlog summary
          content:
            application/json:
              schema:
                type: object
                required: [generatedAt, deliveryMode, summary]
                properties:
                  generatedAt:
                    type: string
                    format: date-time
                  customerId:
                    type: string
                  deliveryMode:
                    type: string
                    enum: [sync, queue]
                  summary:
                    type: object
                    required:
                      - total
                      - queued
                      - sent
                      - delivered
                      - failed
                      - acknowledgedFailed
                      - blocked
                      - canceled
                      - retryDue
                      - retryScheduled
                      - failedWithoutRetry
                      - dueTotal
                      - availableDue
                      - inFlight
                      - expiredLeases
                    properties:
                      total:
                        type: integer
                      queued:
                        type: integer
                      sent:
                        type: integer
                      delivered:
                        type: integer
                      failed:
                        type: integer
                      acknowledgedFailed:
                        type: integer
                        description: Failed outbound messages that operators have acknowledged.
                      blocked:
                        type: integer
                      canceled:
                        type: integer
                      retryDue:
                        type: integer
                      retryScheduled:
                        type: integer
                      failedWithoutRetry:
                        type: integer
                      dueTotal:
                        type: integer
                        description: Queued and retry-due messages, including messages currently leased by a worker.
                      availableDue:
                        type: integer
                        description: Queued and retry-due messages that are not actively leased by a worker.
                      inFlight:
                        type: integer
                        description: Outbound messages with an active delivery worker lease.
                      expiredLeases:
                        type: integer
                        description: Outbound messages whose delivery worker lease has expired.
                      oldestQueuedAt:
                        type: string
                        format: date-time
                      nextRetryAt:
                        type: string
                        format: date-time
                      lastUpdatedAt:
                        type: string
                        format: date-time
  /v1/infrastructure/heartbeats:
    get:
      summary: List raw transport process heartbeats
      description: Root-only operator endpoint for SMTP edge and outbound worker liveness.
      parameters:
        - in: query
          name: component
          schema:
            type: string
            enum: [smtp-edge, outbound-worker]
        - in: query
          name: componentId
          schema:
            type: string
        - in: query
          name: status
          schema:
            type: string
            enum: [ok, degraded]
      responses:
        "200":
          description: Current infrastructure heartbeat records
    post:
      summary: Record a raw transport process heartbeat
      description: Root-only endpoint used by SMTP edge processes. Outbound workers usually write heartbeats directly through Postgres.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [component, componentId]
              properties:
                component:
                  type: string
                  enum: [smtp-edge, outbound-worker]
                componentId:
                  type: string
                hostname:
                  type: string
                status:
                  type: string
                  enum: [ok, degraded]
                metadata:
                  type: object
                  additionalProperties: true
      responses:
        "202":
          description: Heartbeat recorded
  /v1/monitor-runs:
    get:
      summary: List production monitor run history
      description: Root-only operator endpoint for scheduled monitor run records.
      parameters:
        - in: query
          name: status
          schema:
            type: string
            enum: [ok, failed]
        - in: query
          name: since
          schema:
            type: string
            format: date-time
        - in: query
          name: createdBefore
          schema:
            type: string
            format: date-time
        - in: query
          name: limit
          schema:
            type: integer
            minimum: 1
            maximum: 250
        - in: query
          name: cursor
          schema:
            type: string
      responses:
        "200":
          description: Monitor run records with optional nextCursor
    post:
      summary: Record a production monitor run
      description: Root-only endpoint used by scheduled monitor jobs to persist their result in the service.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [report]
              properties:
                report:
                  type: object
                  additionalProperties: true
                alert:
                  type: object
                  additionalProperties: true
                source:
                  type: string
      responses:
        "201":
          description: Monitor run recorded
  /v1/operations/mail-auth:
    get:
      summary: Inspect shared mail-domain authentication DNS
      description: Root-only operator endpoint that verifies the configured shared mail domain's MX, SPF, DKIM, DMARC, address, and reverse-DNS records.
      responses:
        "200":
          description: Mail authentication verification report
          content:
            application/json:
              schema:
                type: object
                required: [ok, status, checkedAt, domain, inboundHostname, records]
                properties:
                  ok:
                    type: boolean
                  status:
                    type: string
                    enum: [verified, pending]
                  checkedAt:
                    type: string
                    format: date-time
                  domain:
                    type: string
                  inboundHostname:
                    type: string
                  records:
                    type: array
                    items:
                      type: object
                      additionalProperties: true
  /v1/operations/status:
    get:
      summary: Inspect consolidated production operations status
      description: Root-only operator endpoint that combines readiness, outbound queue summary, transport heartbeats, recent webhook failures, monitor history, shared-domain mail authentication, and computed warnings into one bounded response.
      parameters:
        - in: query
          name: recentFailureWindowMinutes
          schema:
            type: integer
            minimum: 1
            maximum: 1440
            default: 60
          description: Time window used when listing recent failed webhook deliveries.
      responses:
        "200":
          description: Consolidated production status
          content:
            application/json:
              schema:
                type: object
                required: [status, generatedAt, service, readiness, outbound, infrastructure, webhooks, monitorRuns, mailAuth, warnings]
                properties:
                  status:
                    type: string
                    enum: [ok, degraded, failed]
                  generatedAt:
                    type: string
                    format: date-time
                  service:
                    type: string
                  readiness:
                    type: object
                    additionalProperties: true
                  outbound:
                    type: object
                    additionalProperties: true
                  infrastructure:
                    type: object
                    additionalProperties: true
                  webhooks:
                    type: object
                    additionalProperties: true
                  monitorRuns:
                    type: object
                    additionalProperties: true
                  mailAuth:
                    type: object
                    additionalProperties: true
                  warnings:
                    type: array
                    items:
                      type: object
                      required: [code, severity, message]
                      properties:
                        code:
                          type: string
                        severity:
                          type: string
                          enum: [info, warning, critical]
                        message:
                          type: string
  /v1/operations/launch-evidence:
    get:
      summary: List customer rollout launch evidence
      description: Root-only operator endpoint for launch evidence records used by the launch-readiness gate.
      parameters:
        - in: query
          name: type
          schema:
            type: string
            enum: [external_alerting, deliverability_warmup, backup_restore_drill]
        - in: query
          name: status
          schema:
            type: string
            enum: [verified, in_progress, failed]
        - in: query
          name: since
          schema:
            type: string
            format: date-time
        - in: query
          name: createdBefore
          schema:
            type: string
            format: date-time
        - in: query
          name: limit
          schema:
            type: integer
            minimum: 1
            maximum: 250
        - in: query
          name: cursor
          schema:
            type: string
      responses:
        "200":
          description: Launch evidence records with optional nextCursor
    post:
      summary: Record customer rollout launch evidence
      description: Root-only endpoint for recording verified launch evidence such as alerting tests, deliverability warmup, and backup/restore drills.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [type, summary]
              properties:
                type:
                  type: string
                  enum: [external_alerting, deliverability_warmup, backup_restore_drill]
                status:
                  type: string
                  enum: [verified, in_progress, failed]
                  default: verified
                summary:
                  type: string
                details:
                  type: object
                  additionalProperties: true
                recordedBy:
                  type: string
      responses:
        "201":
          description: Launch evidence recorded
  /v1/operations/launch-readiness:
    get:
      summary: Inspect customer rollout launch readiness
      description: Root-only operator endpoint that converts production operations signals into rollout gates. It separates pilot blockers from broad-rollout warnings such as missing external alerting, deliverability warmup evidence, and backup/restore drill evidence.
      parameters:
        - in: query
          name: recentFailureWindowMinutes
          schema:
            type: integer
            minimum: 1
            maximum: 1440
            default: 60
          description: Time window used when checking recent failed webhook deliveries.
      responses:
        "200":
          description: Launch-readiness gate report
          content:
            application/json:
              schema:
                type: object
                required: [status, generatedAt, readyForCustomerPilot, readyForBroadRollout, blockers, warnings, gates, operations]
                properties:
                  status:
                    type: string
                    enum: [blocked, pilot_ready, broad_rollout_ready]
                  generatedAt:
                    type: string
                    format: date-time
                  readyForCustomerPilot:
                    type: boolean
                  readyForBroadRollout:
                    type: boolean
                  blockers:
                    type: array
                    items:
                      type: object
                      additionalProperties: true
                  warnings:
                    type: array
                    items:
                      type: object
                      additionalProperties: true
                  gates:
                    type: array
                    items:
                      type: object
                      required: [code, status, severity, message]
                      properties:
                        code:
                          type: string
                        status:
                          type: string
                          enum: [pass, fail, warn]
                        severity:
                          type: string
                          enum: [blocker, warning]
                        message:
                          type: string
                        evidence:
                          type: object
                          additionalProperties: true
                  operations:
                    type: object
                    additionalProperties: true
  /v1/outbound-rate-limits:
    get:
      summary: Inspect outbound send counters for the active rate window
      parameters:
        - in: query
          name: customerId
          schema:
            type: string
        - in: query
          name: inboxId
          schema:
            type: string
      responses:
        "200":
          description: Current customer and inbox outbound rate-limit counters
  /v1/inbound-rate-limits:
    get:
      summary: Inspect inbound receive counters for the active rate window
      parameters:
        - in: query
          name: customerId
          schema:
            type: string
        - in: query
          name: inboxId
          schema:
            type: string
      responses:
        "200":
          description: Current customer and inbox inbound rate-limit counters
  /v1/suppressions:
    get:
      summary: List suppressed recipient email addresses
      description: Results are ordered newest first. Use limit and cursor to page through long-lived suppression history.
      parameters:
        - in: query
          name: customerId
          schema:
            type: string
        - in: query
          name: email
          schema:
            type: string
        - in: query
          name: reason
          schema:
            type: string
            enum: [manual, bounce, complaint, unsubscribe]
        - in: query
          name: since
          schema:
            type: string
            format: date-time
        - in: query
          name: createdBefore
          schema:
            type: string
            format: date-time
        - in: query
          name: limit
          schema:
            type: integer
            minimum: 1
            maximum: 250
        - in: query
          name: cursor
          schema:
            type: string
      responses:
        "200":
          description: Suppressed recipients with optional nextCursor
    post:
      summary: Manually suppress a recipient email address
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [customerId, email, reason]
              properties:
                customerId:
                  type: string
                email:
                  type: string
                reason:
                  type: string
                  enum: [manual, bounce, complaint, unsubscribe]
                note:
                  type: string
      responses:
        "201":
          description: Suppression created or updated
  /v1/lists:
    get:
      summary: List send, receive, and reply allow/block entries
      description: Results are ordered newest first. Entries can be scoped at customer, deployment, or inbox level. Inbox entries override deployment and customer entries; deployment entries override customer entries.
      parameters:
        - in: query
          name: customerId
          required: true
          schema:
            type: string
        - in: query
          name: deploymentId
          schema:
            type: string
        - in: query
          name: inboxId
          schema:
            type: string
        - in: query
          name: direction
          schema:
            type: string
            enum: [send, receive, reply]
        - in: query
          name: action
          schema:
            type: string
            enum: [allow, block]
        - in: query
          name: matcher
          schema:
            type: string
            enum: [email, domain]
        - in: query
          name: value
          schema:
            type: string
        - in: query
          name: limit
          schema:
            type: integer
            minimum: 1
            maximum: 250
        - in: query
          name: cursor
          schema:
            type: string
      responses:
        "200":
          description: List entries with optional nextCursor
    post:
      summary: Create a send, receive, or reply allow/block entry
      description: Creates or updates a scoped list entry. Use action=allow to make matching email/domain values the only permitted values at that scope; use action=block to deny matching values.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [customerId, direction, action, value]
              properties:
                customerId:
                  type: string
                deploymentId:
                  type: string
                inboxId:
                  type: string
                direction:
                  type: string
                  enum: [send, receive, reply]
                action:
                  type: string
                  enum: [allow, block]
                matcher:
                  type: string
                  enum: [email, domain]
                value:
                  type: string
                note:
                  type: string
      responses:
        "201":
          description: List entry created or updated
  /v1/lists/{entryId}:
    delete:
      summary: Delete a list entry
      parameters:
        - in: path
          name: entryId
          required: true
          schema:
            type: string
      responses:
        "200":
          description: Deleted list entry
        "404":
          description: List entry not found
  /v1/audit-logs:
    get:
      summary: List audit logs with cursor pagination
      parameters:
        - in: query
          name: customerId
          schema:
            type: string
        - in: query
          name: deploymentId
          schema:
            type: string
        - in: query
          name: agentId
          schema:
            type: string
        - in: query
          name: inboxId
          schema:
            type: string
        - in: query
          name: messageId
          schema:
            type: string
        - in: query
          name: action
          schema:
            type: string
        - in: query
          name: since
          schema:
            type: string
            format: date-time
        - in: query
          name: createdBefore
          schema:
            type: string
            format: date-time
        - in: query
          name: limit
          schema:
            type: integer
            minimum: 1
            maximum: 250
        - in: query
          name: cursor
          schema:
            type: string
      responses:
        "200":
          description: Audit log page
  /v1/events:
    get:
      summary: List durable events with cursor pagination
      parameters:
        - in: query
          name: customerId
          schema:
            type: string
        - in: query
          name: deploymentId
          schema:
            type: string
        - in: query
          name: agentId
          schema:
            type: string
        - in: query
          name: inboxId
          schema:
            type: string
        - in: query
          name: type
          schema:
            type: string
        - in: query
          name: since
          schema:
            type: string
            format: date-time
        - in: query
          name: createdBefore
          schema:
            type: string
            format: date-time
        - in: query
          name: limit
          schema:
            type: integer
            minimum: 1
            maximum: 250
        - in: query
          name: cursor
          schema:
            type: string
      responses:
        "200":
          description: Event page
  /v1/events/stream:
    get:
      summary: Stream live events
      description: Server-sent event stream for live agent notifications. Uses the same filters and events:read scope as the durable events endpoint.
      parameters:
        - in: query
          name: customerId
          schema:
            type: string
        - in: query
          name: deploymentId
          schema:
            type: string
        - in: query
          name: agentId
          schema:
            type: string
        - in: query
          name: inboxId
          schema:
            type: string
        - in: query
          name: type
          schema:
            type: string
      responses:
        "200":
          description: text/event-stream response
  /v1/events/ws:
    get:
      summary: Stream live events over WebSocket
      description: Native WebSocket endpoint. Connect with wss://mail-api.noinfra.ai/v1/events/ws and pass an API key via Authorization, x-api-key, or api_key query parameter. Server messages are JSON frames with type ready, event, subscribed, events, pong, or error. Client messages can be {"type":"ping"}, {"type":"subscribe","customerId":"cus_..."}, or {"type":"list","limit":25}.
      parameters:
        - in: query
          name: api_key
          schema:
            type: string
        - in: query
          name: customerId
          schema:
            type: string
        - in: query
          name: deploymentId
          schema:
            type: string
        - in: query
          name: agentId
          schema:
            type: string
        - in: query
          name: inboxId
          schema:
            type: string
        - in: query
          name: type
          schema:
            type: string
      responses:
        "101":
          description: WebSocket upgrade accepted
  /v1/events/websocket:
    get:
      summary: Alias for /v1/events/ws
      responses:
        "101":
          description: WebSocket upgrade accepted
