openapi: 3.1.0
info:
  title: Primitive API
  version: 1.0.0
  description: |-
    Primitive is email infrastructure for AI agents. The Primitive API lets you manage domains, emails, webhook endpoints,
    filters, and account settings programmatically.

    ## Authentication

    Most endpoints require a Bearer token in the `Authorization` header:

    ```
    Authorization: Bearer prim_<your_api_key>
    Authorization: Bearer prim_oat_<oauth_access_token>
    ```

    API keys and OAuth access tokens are org-scoped. Create and manage them in your dashboard
    under Settings > API Keys. CLI login plus CLI/agent signup endpoints
    explicitly declare `security: []`; they do not require an API key because
    they are used to create OAuth CLI sessions.

    ## Rate Limiting

    The API enforces a sliding window rate limit of **120 requests per
    60 seconds** per organization. When exceeded, the API returns `429`
    with a `Retry-After` header indicating how many seconds to wait.

    ## Pagination

    List endpoints use cursor-based pagination. Responses include a
    `meta` object with `total`, `limit`, and `cursor` fields. Pass the
    `cursor` value as a query parameter to fetch the next page. When
    `cursor` is `null`, there are no more results.

    ## Response Format

    All responses use a consistent envelope:

    ```json
    {
      "success": true,
      "data": { ... },
      "meta": { "total": 42, "limit": 50, "cursor": "..." }
    }
    ```

    Errors follow the same pattern:

    ```json
    {
      "success": false,
      "error": { "code": "not_found", "message": "Email not found" }
    }
    ```

    ## Webhook signing

    Outbound webhook deliveries (configured via the `endpoints` API)
    are signed so receivers can verify they came from Primitive and
    have not been tampered with in transit. The signing scheme is
    deliberately simple so it can be reimplemented in any language
    in a few lines. The Node SDK's `verifyWebhookSignature` helper
    is the reference implementation; the wire details below let you
    write a verifier in Python, Go, Ruby, etc. without reading our
    source.

    **Header**: `Primitive-Signature: t=<unix-seconds>,v1=<hex>`

    A legacy `MyMX-Signature` header is also sent on every delivery
    with the same value, retained for back-compatibility with
    integrations written before the rename. New code should read
    `Primitive-Signature`.

    **Signed string**: `${timestamp}.${rawBody}` where `timestamp`
    is the Unix-seconds integer from the `t=` parameter and
    `rawBody` is the exact bytes of the HTTP request body BEFORE
    any JSON decoding. Verify against the raw body, not a
    re-serialized parse, or you will silently mismatch on
    insignificant whitespace.

    **Signature**: HMAC-SHA256 of the signed string, hex-encoded
    (lowercase). Use the account's webhook secret as the HMAC key,
    as a UTF-8 byte sequence.

    **Secret**: returned by `GET /account/webhook-secret`. The
    string looks base64-shaped (e.g. `XNHBBW8VqoBjRfNs1tkZj11jTk...`)
    but is NOT base64; use it AS-IS as a UTF-8 string for the HMAC
    key. Base64-decoding before HMAC will silently produce
    mismatched signatures.

    **Tolerance**: by convention, reject deliveries whose `t=`
    timestamp is more than 5 minutes off your wall-clock to defend
    against replay attacks. The Node SDK's helper enforces this by
    default.

    **Verification recipe** (any language):

    ```
    1. Read the raw HTTP body (do not parse).
    2. Read `Primitive-Signature: t=<ts>,v1=<sig>`.
    3. Reject if abs(now - ts) > 300 seconds.
    4. expected = HMAC_SHA256_hex(secret_utf8, f"{ts}.{rawBody}")
    5. Constant-time compare expected to sig. Reject if not equal.
    ```

    For Node, use `verifyWebhookSignature` from
    `@primitivedotdev/sdk/webhook` (or the higher-level
    `handleWebhook` helper if you want a one-liner). For other
    languages, the recipe above is everything you need.

    Test deliveries: `POST /endpoints/{id}/test` triggers a fake
    delivery to your endpoint URL, signed with your real account
    secret, so you can confirm verification end-to-end without
    needing real inbound mail. The test response carries the exact
    `signature` header value sent on the wire so you can compare
    strings directly.


    ## Errors

    Every error response is the same JSON envelope (`{ "success": false, "error": { "code", "message" } }`), served as `application/json` with HTTP status codes, following the RFC 7807 problem-details shape. The `error.code` is a stable machine-readable string and `error.message` is human-readable.

    ## Authorization and roles

    Access is governed by organization role-based access control. Every organization member holds one of three roles — `owner`, `admin`, or `member` — and a credential inherits a role. **API keys** always act at `member` level, regardless of the role of the user who created them, so an API key can never perform owner- or admin-only actions. **OAuth access tokens** act with the authorizing user's current organization role, resolved on each request. Every operation in this spec is part of the member-level surface, so any valid credential can call it. Organization administration that is not part of this API — billing and organization settings — requires an `owner` or `admin` and is performed in the dashboard. Fine-grained per-key scopes (e.g. a send-only or read-only key) are on the roadmap; today the role model is the unit of access control.

    ## Versioning

    The current stable API is **v1**. All endpoints are served under `/v1/` and are covered by a backward-compatibility guarantee: existing fields and status codes will not change without a deprecation notice.

    Breaking changes are announced at least 6 months in advance via changelog and email. Deprecated operations and fields are marked `x-deprecated: true` in the spec and carry a plain-English description of the replacement. The `v1` path prefix is guaranteed stable indefinitely; backward-compatible additions (new optional fields, new endpoints) may be made at any time without a version bump.
  contact:
    name: Primitive
    url: https://primitive.dev
  license:
    name: Proprietary
    url: https://primitive.dev/terms
  x-stability-level: stable
  x-deprecation-policy: "Breaking changes are announced at least 6 months in advance. Deprecated fields carry x-deprecated: true. The current stable version is v1."
servers:
  - url: https://api.primitive.dev/v1
    description: Canonical API host (PRIMITIVE_API_BASE_URL). Carries every public API operation.
tags:
  - name: CLI
    description: Browser-assisted CLI authentication
  - name: Agent
    description: Agent signup and authentication
  - name: Account
    description: Manage your account settings, storage, and webhook secret
  - name: Domains
    description: Claim, verify, and manage email domains
  - name: Inbox
    description: Check inbound email setup and processing readiness
  - name: Emails
    description: List, inspect, and manage received emails
  - name: Search
    description: Semantic and hybrid search across received and sent mail
  - name: Sending
    description: Send outbound emails through the Primitive API
  - name: Threads
    description: Conversation threads spanning received and sent emails
  - name: Endpoints
    description: Manage webhook endpoints that receive email events
  - name: Filters
    description: Manage whitelist and blocklist filter rules
  - name: Routes
    description: |
      Recipient routing: route inbound mail to a single destination per recipient
      address. Rules bind an address pattern (exact or wildcard) to an endpoint;
      `function_id` routes an address to a function, minting its route-target
      endpoint.
  - name: Payments
    description: |
      Collect and pay stablecoin (USDC) payments with x402. Settlement is
      non-custodial: funds move directly from payer to payee on-chain via an
      EIP-3009 authorization the payer signs with their own key, and Primitive
      never holds funds. The payee registers a payout address and creates a
      challenge; the payer signs and settles it under a configurable spend
      policy (kill-switch, per-payment and per-day caps, payee allowlist).
  - name: Wake
    description: |
      Wake scheduling: schedule and send typed wake commands to your own
      functions over real DKIM-signed email on a cron cadence, and manage the
      per-target allowlist that authorizes which senders may wake a function.
  - name: Webhook Deliveries
    description: View and replay webhook delivery attempts
  - name: Functions
    description: |
      Deploy JavaScript handlers that run on inbound mail. Each function
      is a single ESM module whose default export is an object with an
      async `fetch(request, env)` method, in the shape of a Workers-style
      handler. Primitive signs each delivery and forwards the
      `Primitive-Signature` header to the handler; verify the raw request
      body with `PRIMITIVE_WEBHOOK_SECRET` before trusting the parsed event.
      The `event` field is `email.received` for normal inbound mail, or a
      machine-mail type (`email.bounced`, `email.tls_report`,
      `email.dmarc_report`, `email.dmarc_failure`) for bounces and reports;
      the payload shape is otherwise identical. Code runs on
      Primitive's edge runtime; there is no infrastructure to manage.
      Secrets land in `env` as encrypted bindings and are refreshed on
      every redeploy.
  - name: Memories
    description: |
      Durable org-scoped or function-scoped JSON key-value storage for
      agents and functions. Keys are caller-defined. Function scope is
      always addressed by the function id UUID, not by function name.
  - name: Registries
    description: |
      The Agent Registry: ownable directories of agents, addressable by a
      registry-scoped handle. A registry's publish policy (owner_only, request,
      or open) decides whether a publish lists immediately or pends owner
      approval. An agent is defined once with a globally unique,
      reachability-verified address, then published into any registry under a
      handle. Discovery reads (list, resolve, get) are public for public
      registries; managing a registry and moderating requests use the owner's
      API key.
  - name: Templates
    description: Public Function template registry reads used to browse installable agent templates.
  - name: Demo
    description: Public, no-account sandbox operations that mirror authenticated endpoints with synthetic data so an agent can learn the request and response shapes before signing up.
  - name: Discovery
    description: Unauthenticated entry point that lists the API base URL, how to obtain credentials, and the operations callable without a token.
  - name: Service
    description: Operational endpoints such as the unauthenticated health/liveness probe.
paths:
  /cli/login/start:
    post:
      operationId: startCliLogin
      summary: Start CLI browser login
      description: |
        Starts a browser-assisted CLI login session. The response includes a
        device code for polling and a user code that the user approves in the
        browser. This endpoint does not require an API key.
      tags:
        - CLI
      security: []
      requestBody:
        required: false
        content:
          application/json:
            schema:
              type: object
              additionalProperties: false
              properties:
                device_name:
                  type: string
                  minLength: 1
                  maxLength: 80
                  description: Human-readable device name shown during browser approval
                metadata:
                  type: object
                  additionalProperties: true
                  description: Optional client metadata stored with the login session; serialized JSON must be 2048 bytes or fewer
      responses:
        "201":
          description: CLI login session created
          headers:
            Cache-Control:
              schema:
                type: string
              description: Always `no-store`
          content:
            application/json:
              schema:
                allOf:
                  - type: object
                    properties:
                      success:
                        type: boolean
                        const: true
                    required:
                      - success
                      - data
                  - type: object
                    properties:
                      data:
                        type: object
                        properties:
                          device_code:
                            type: string
                            description: Opaque code used by the CLI to poll for approval
                          user_code:
                            type: string
                            pattern: ^[BCDFGHJKLMNPQRSTVWXZ]{4}-[BCDFGHJKLMNPQRSTVWXZ]{4}$
                            description: Short code the user confirms in the browser
                          verification_uri:
                            type: string
                            description: Browser URL where the user approves the login
                          verification_uri_complete:
                            type: string
                            description: Browser URL with the user code prefilled
                          expires_in:
                            type: integer
                            description: Seconds until the login session expires
                          interval:
                            type: integer
                            description: Minimum seconds between poll requests
                        required:
                          - device_code
                          - user_code
                          - verification_uri
                          - verification_uri_complete
                          - expires_in
                          - interval
        "400":
          $ref: "#/components/responses/ValidationError"
          description: Invalid request parameters
        "429":
          $ref: "#/components/responses/RateLimited"
          description: Rate limit exceeded
      parameters:
        - name: Idempotency-Key
          in: header
          required: false
          description: Optional client-supplied idempotency key. Retrying a request with the same key returns the original result instead of performing the action a second time; if omitted the server derives one from the canonical payload hash. Safe to retry network failures without duplicating side effects.
          schema:
            type: string
            minLength: 1
            maxLength: 255
  /cli/login/poll:
    post:
      operationId: pollCliLogin
      summary: Poll CLI browser login
      description: |
        Polls a CLI login session until the browser approval either succeeds,
        is denied, expires, or is polled too quickly. The OAuth token set is
        created only after approval and is returned exactly once.
      tags:
        - CLI
      security: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              additionalProperties: false
              properties:
                device_code:
                  type: string
                  minLength: 1
              required:
                - device_code
      responses:
        "200":
          description: CLI login approved and OAuth token set created
          headers:
            Cache-Control:
              schema:
                type: string
              description: Always `no-store`
          content:
            application/json:
              schema:
                allOf:
                  - type: object
                    properties:
                      success:
                        type: boolean
                        const: true
                    required:
                      - success
                      - data
                  - type: object
                    properties:
                      data:
                        type: object
                        properties:
                          api_key:
                            type: string
                            description: Legacy alias for access_token. New CLI builds should persist access_token and refresh_token.
                          key_id:
                            type: string
                            format: uuid
                            description: Legacy alias for oauth_grant_id
                          key_prefix:
                            type: string
                            description: Legacy display prefix derived from access_token
                          access_token:
                            type: string
                            description: OAuth access token for CLI API authentication
                          refresh_token:
                            type: string
                            description: OAuth refresh token used by the CLI to renew access
                          token_type:
                            type: string
                            enum:
                              - Bearer
                          expires_in:
                            type: integer
                            description: Seconds until access_token expires
                          auth_method:
                            type: string
                            enum:
                              - oauth
                          oauth_grant_id:
                            type: string
                            format: uuid
                          oauth_client_id:
                            type: string
                          org_id:
                            type: string
                            format: uuid
                          org_name:
                            type:
                              - string
                              - "null"
                        required:
                          - api_key
                          - key_id
                          - key_prefix
                          - access_token
                          - refresh_token
                          - token_type
                          - expires_in
                          - auth_method
                          - oauth_grant_id
                          - oauth_client_id
                          - org_id
                          - org_name
        "400":
          $ref: "#/components/responses/ValidationError"
          description: Invalid request, pending authorization, slow polling, expired token, or invalid device code
        "403":
          $ref: "#/components/responses/Forbidden"
          description: CLI login was denied in the browser
      parameters:
        - name: Idempotency-Key
          in: header
          required: false
          description: Optional client-supplied idempotency key. Retrying a request with the same key returns the original result instead of performing the action a second time; if omitted the server derives one from the canonical payload hash. Safe to retry network failures without duplicating side effects.
          schema:
            type: string
            minLength: 1
            maxLength: 255
  /cli/signup/start:
    post:
      operationId: startCliSignup
      summary: Start CLI account signup
      description: |
        Starts a terminal-native CLI signup. `signup_code` is optional;
        omit it to sign up without one. The API creates a pending signup
        session, sends an email verification code, and returns an opaque
        signup token used by the resend and verify steps. This endpoint
        does not require an API key.
      tags:
        - CLI
      security: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              additionalProperties: false
              properties:
                email:
                  type: string
                  format: email
                  maxLength: 254
                signup_code:
                  type: string
                  minLength: 1
                  maxLength: 128
                  description: Optional signup code. Omit if you do not have one.
                terms_accepted:
                  type: boolean
                  const: true
                  description: Must be true to confirm acceptance of Primitive's Terms of Service and Privacy Policy
                device_name:
                  type: string
                  minLength: 1
                  maxLength: 80
                  description: Human-readable device name used for the created CLI OAuth grant
                metadata:
                  type: object
                  additionalProperties: true
                  description: Optional client metadata stored with the signup session; serialized JSON must be 2048 bytes or fewer
              required:
                - email
                - terms_accepted
      responses:
        "201":
          description: CLI signup session created and verification email sent
          headers:
            Cache-Control:
              schema:
                type: string
              description: Always `no-store`
          content:
            application/json:
              schema:
                allOf:
                  - type: object
                    properties:
                      success:
                        type: boolean
                        const: true
                    required:
                      - success
                      - data
                  - type: object
                    properties:
                      data:
                        type: object
                        properties:
                          signup_token:
                            type: string
                            description: Opaque token used to verify or resend the pending CLI signup
                          email:
                            type: string
                            format: email
                          expires_in:
                            type: integer
                            description: Seconds until the pending signup expires
                          resend_after:
                            type: integer
                            description: Minimum seconds before requesting another verification email
                          verification_code_length:
                            type: integer
                            description: Number of digits in the emailed verification code
                        required:
                          - signup_token
                          - email
                          - expires_in
                          - resend_after
                          - verification_code_length
        "400":
          $ref: "#/components/responses/ValidationError"
          description: Invalid request parameters
        "429":
          $ref: "#/components/responses/RateLimited"
          description: Rate limit exceeded
      parameters:
        - name: Idempotency-Key
          in: header
          required: false
          description: Optional client-supplied idempotency key. Retrying a request with the same key returns the original result instead of performing the action a second time; if omitted the server derives one from the canonical payload hash. Safe to retry network failures without duplicating side effects.
          schema:
            type: string
            minLength: 1
            maxLength: 255
  /cli/signup/resend:
    post:
      operationId: resendCliSignupVerification
      summary: Resend CLI signup verification code
      description: |
        Sends a new email verification code for a pending CLI signup session.
        This endpoint does not require an API key.
      tags:
        - CLI
      security: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              additionalProperties: false
              properties:
                signup_token:
                  type: string
                  minLength: 1
              required:
                - signup_token
      responses:
        "200":
          description: Verification email resent
          headers:
            Cache-Control:
              schema:
                type: string
              description: Always `no-store`
          content:
            application/json:
              schema:
                allOf:
                  - type: object
                    properties:
                      success:
                        type: boolean
                        const: true
                    required:
                      - success
                      - data
                  - type: object
                    properties:
                      data:
                        type: object
                        properties:
                          email:
                            type: string
                            format: email
                          expires_in:
                            type: integer
                            description: Seconds until the pending signup expires
                          resend_after:
                            type: integer
                            description: Minimum seconds before requesting another verification email
                          verification_code_length:
                            type: integer
                            description: Number of digits in the emailed verification code
                        required:
                          - email
                          - expires_in
                          - resend_after
                          - verification_code_length
        "400":
          $ref: "#/components/responses/ValidationError"
          description: Invalid token or expired token
        "429":
          $ref: "#/components/responses/RateLimited"
          description: Global rate limit exceeded or resend requested too quickly
      parameters:
        - name: Idempotency-Key
          in: header
          required: false
          description: Optional client-supplied idempotency key. Retrying a request with the same key returns the original result instead of performing the action a second time; if omitted the server derives one from the canonical payload hash. Safe to retry network failures without duplicating side effects.
          schema:
            type: string
            minLength: 1
            maxLength: 255
  /cli/signup/verify:
    post:
      operationId: verifyCliSignup
      summary: Verify CLI signup and create OAuth session
      description: |
        Verifies the email code for a CLI signup session and creates the
        account. When the session was started with a `signup_code`, the
        reserved code is redeemed; sessions started without a code skip
        the redemption step. Either way an org-scoped OAuth CLI session
        is created and the token set is returned exactly once. This
        endpoint does not require an API key.
      tags:
        - CLI
      security: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              additionalProperties: false
              properties:
                signup_token:
                  type: string
                  minLength: 1
                verification_code:
                  type: string
                  minLength: 1
                  maxLength: 32
                password:
                  type: string
                  minLength: 1
                  maxLength: 1024
              required:
                - signup_token
                - verification_code
      responses:
        "200":
          description: CLI signup verified and OAuth token set created
          headers:
            Cache-Control:
              schema:
                type: string
              description: Always `no-store`
          content:
            application/json:
              schema:
                allOf:
                  - type: object
                    properties:
                      success:
                        type: boolean
                        const: true
                    required:
                      - success
                      - data
                  - type: object
                    properties:
                      data:
                        type: object
                        properties:
                          api_key:
                            type: string
                            description: Legacy alias for access_token. New CLI builds should persist access_token and refresh_token.
                          key_id:
                            type: string
                            format: uuid
                            description: Legacy alias for oauth_grant_id
                          key_prefix:
                            type: string
                            description: Legacy display prefix derived from access_token
                          access_token:
                            type: string
                            description: OAuth access token for CLI API authentication
                          refresh_token:
                            type: string
                            description: OAuth refresh token used by the CLI to renew access
                          token_type:
                            type: string
                            enum:
                              - Bearer
                          expires_in:
                            type: integer
                            description: Seconds until access_token expires
                          auth_method:
                            type: string
                            enum:
                              - oauth
                          oauth_grant_id:
                            type: string
                            format: uuid
                          oauth_client_id:
                            type: string
                          org_id:
                            type: string
                            format: uuid
                          org_name:
                            type:
                              - string
                              - "null"
                        required:
                          - api_key
                          - key_id
                          - key_prefix
                          - access_token
                          - refresh_token
                          - token_type
                          - expires_in
                          - auth_method
                          - oauth_grant_id
                          - oauth_client_id
                          - org_id
                          - org_name
        "400":
          $ref: "#/components/responses/ValidationError"
          description: Invalid request, invalid verification code, expired token, invalid signup code, rejected password, or account creation failure
        "429":
          $ref: "#/components/responses/RateLimited"
          description: Rate limit exceeded
      parameters:
        - name: Idempotency-Key
          in: header
          required: false
          description: Optional client-supplied idempotency key. Retrying a request with the same key returns the original result instead of performing the action a second time; if omitted the server derives one from the canonical payload hash. Safe to retry network failures without duplicating side effects.
          schema:
            type: string
            minLength: 1
            maxLength: 255
  /agent/signup/start:
    post:
      operationId: startAgentSignup
      summary: Start agent account signup
      description: |
        Starts an agent-native signup session. `signup_code` is optional;
        omit it to sign up without one. The API creates a pending signup
        session, sends an email verification code, and returns an opaque
        signup token used by the resend and verify steps. This endpoint
        does not require an API key.
      tags:
        - Agent
      security: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              additionalProperties: false
              properties:
                email:
                  type: string
                  format: email
                  maxLength: 254
                signup_code:
                  type: string
                  minLength: 1
                  maxLength: 128
                  description: Optional signup code. Omit if you do not have one.
                terms_accepted:
                  type: boolean
                  const: true
                  description: Must be true to confirm acceptance of Primitive's Terms of Service and Privacy Policy
                device_name:
                  type: string
                  minLength: 1
                  maxLength: 80
                  description: Human-readable device name used for the created agent OAuth session
                metadata:
                  type: object
                  additionalProperties: true
                  description: Optional client metadata stored with the signup session; serialized JSON must be 2048 bytes or fewer
              required:
                - email
                - terms_accepted
      responses:
        "201":
          description: Agent signup session created and verification email sent
          headers:
            Cache-Control:
              schema:
                type: string
              description: Always `no-store`
          content:
            application/json:
              schema:
                allOf:
                  - type: object
                    properties:
                      success:
                        type: boolean
                        const: true
                    required:
                      - success
                      - data
                  - type: object
                    properties:
                      data:
                        type: object
                        properties:
                          signup_token:
                            type: string
                            description: Opaque token used to verify or resend the pending agent signup
                          email:
                            type: string
                            format: email
                          expires_in:
                            type: integer
                            description: Seconds until the pending signup expires
                          resend_after:
                            type: integer
                            description: Minimum seconds before requesting another verification email
                          verification_code_length:
                            type: integer
                            description: Number of digits in the emailed verification code
                        required:
                          - signup_token
                          - email
                          - expires_in
                          - resend_after
                          - verification_code_length
        "400":
          $ref: "#/components/responses/ValidationError"
          description: Invalid request parameters
        "429":
          $ref: "#/components/responses/RateLimited"
          description: Rate limit exceeded
      parameters:
        - name: Idempotency-Key
          in: header
          required: false
          description: Optional client-supplied idempotency key. Retrying a request with the same key returns the original result instead of performing the action a second time; if omitted the server derives one from the canonical payload hash. Safe to retry network failures without duplicating side effects.
          schema:
            type: string
            minLength: 1
            maxLength: 255
  /agent/signup/resend:
    post:
      operationId: resendAgentSignupVerification
      summary: Resend agent signup verification code
      description: |
        Sends a new email verification code for a pending agent signup session.
        This endpoint does not require an API key.
      tags:
        - Agent
      security: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              additionalProperties: false
              properties:
                signup_token:
                  type: string
                  minLength: 1
              required:
                - signup_token
      responses:
        "200":
          description: Verification email resent
          headers:
            Cache-Control:
              schema:
                type: string
              description: Always `no-store`
          content:
            application/json:
              schema:
                allOf:
                  - type: object
                    properties:
                      success:
                        type: boolean
                        const: true
                    required:
                      - success
                      - data
                  - type: object
                    properties:
                      data:
                        type: object
                        properties:
                          email:
                            type: string
                            format: email
                          expires_in:
                            type: integer
                            description: Seconds until the pending signup expires
                          resend_after:
                            type: integer
                            description: Minimum seconds before requesting another verification email
                          verification_code_length:
                            type: integer
                            description: Number of digits in the emailed verification code
                        required:
                          - email
                          - expires_in
                          - resend_after
                          - verification_code_length
        "400":
          $ref: "#/components/responses/ValidationError"
          description: Invalid token or expired token
        "429":
          $ref: "#/components/responses/RateLimited"
          description: Global rate limit exceeded or resend requested too quickly
      parameters:
        - name: Idempotency-Key
          in: header
          required: false
          description: Optional client-supplied idempotency key. Retrying a request with the same key returns the original result instead of performing the action a second time; if omitted the server derives one from the canonical payload hash. Safe to retry network failures without duplicating side effects.
          schema:
            type: string
            minLength: 1
            maxLength: 255
  /agent/signup/verify:
    post:
      operationId: verifyAgentSignup
      summary: Verify agent signup and create OAuth tokens
      description: |
        Verifies the email code for an agent signup session and creates
        the account when needed. When the session was started with a
        `signup_code`, the reserved code is redeemed; sessions started
        without a code skip the redemption step. An org-scoped OAuth
        session for CLI authentication is minted and the raw tokens are
        returned exactly once. For existing users, the optional `org_id`
        selects which accessible workspace should receive the new
        session (no signup-code redemption is performed for existing
        users regardless of how the session was started).
      tags:
        - Agent
      security: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              additionalProperties: false
              properties:
                signup_token:
                  type: string
                  minLength: 1
                verification_code:
                  type: string
                  minLength: 1
                  maxLength: 32
                org_id:
                  type: string
                  format: uuid
                  description: Optional workspace id to target when the verified email already belongs to multiple workspaces
              required:
                - signup_token
                - verification_code
      responses:
        "200":
          description: Agent signup verified and OAuth tokens created
          headers:
            Cache-Control:
              schema:
                type: string
              description: Always `no-store`
          content:
            application/json:
              schema:
                allOf:
                  - type: object
                    properties:
                      success:
                        type: boolean
                        const: true
                    required:
                      - success
                      - data
                  - type: object
                    properties:
                      data:
                        type: object
                        properties:
                          api_key:
                            type: string
                            description: Legacy alias for access_token. New CLI builds should persist access_token and refresh_token.
                          key_id:
                            type: string
                            format: uuid
                            description: Legacy alias for oauth_grant_id
                          key_prefix:
                            type: string
                            description: Legacy display prefix derived from access_token
                          access_token:
                            type: string
                            description: OAuth access token for CLI API authentication
                          refresh_token:
                            type: string
                            description: OAuth refresh token used by the CLI to renew access
                          token_type:
                            type: string
                            enum:
                              - Bearer
                          expires_in:
                            type: integer
                            description: Seconds until access_token expires
                          auth_method:
                            type: string
                            enum:
                              - oauth
                          oauth_grant_id:
                            type: string
                            format: uuid
                          oauth_client_id:
                            type: string
                          org_id:
                            type: string
                            format: uuid
                          org_name:
                            type:
                              - string
                              - "null"
                          orgs:
                            type: array
                            items:
                              type: object
                              properties:
                                id:
                                  type: string
                                  format: uuid
                                name:
                                  type:
                                    - string
                                    - "null"
                              required:
                                - id
                                - name
                            description: Workspaces available to the verified email. The minted session targets `org_id`.
                        required:
                          - api_key
                          - key_id
                          - key_prefix
                          - access_token
                          - refresh_token
                          - token_type
                          - expires_in
                          - auth_method
                          - oauth_grant_id
                          - oauth_client_id
                          - org_id
                          - org_name
                          - orgs
        "400":
          $ref: "#/components/responses/ValidationError"
          description: Invalid request, invalid verification code, expired token, invalid signup code, or account creation failure
        "403":
          $ref: "#/components/responses/Forbidden"
          description: Authenticated caller lacks permission for the operation
        "409":
          $ref: "#/components/responses/Conflict"
          description: Existing account is not in a usable workspace state
        "429":
          $ref: "#/components/responses/RateLimited"
          description: Rate limit exceeded
      parameters:
        - name: Idempotency-Key
          in: header
          required: false
          description: Optional client-supplied idempotency key. Retrying a request with the same key returns the original result instead of performing the action a second time; if omitted the server derives one from the canonical payload hash. Safe to retry network failures without duplicating side effects.
          schema:
            type: string
            minLength: 1
            maxLength: 255
  /agent/accounts:
    post:
      operationId: createAgentAccount
      summary: Create an emailless agent account
      description: |
        Creates an emailless agent account without authentication and returns a
        one-time API key (prefixed `prim_`) plus a provisioned managed inbox.
        The account is on the `agent` plan: reply-only (it can send only to
        addresses that have already sent it authenticated mail) with tight send
        limits. Use the returned `api_key` as a Bearer token on later calls. The
        account can be upgraded to a full developer account by confirming an
        email through the claim flow. This endpoint does not require an API key.
      tags:
        - Agent
      security: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              additionalProperties: false
              properties:
                terms_accepted:
                  type: boolean
                  enum:
                    - true
                  description: Must be true to accept the Terms of Service and Privacy Policy.
                device_name:
                  type: string
                  minLength: 1
                  maxLength: 80
                  description: Optional label for the device or agent creating the account.
              required:
                - terms_accepted
      responses:
        "200":
          description: Agent account created; the API key is returned once
          headers:
            Cache-Control:
              schema:
                type: string
              description: Always `no-store`
          content:
            application/json:
              schema:
                allOf:
                  - type: object
                    properties:
                      success:
                        type: boolean
                        const: true
                    required:
                      - success
                      - data
                  - type: object
                    properties:
                      data:
                        type: object
                        properties:
                          api_key:
                            type: string
                            description: One-time API key (prefixed `prim_`). Shown once; store it securely.
                          org_id:
                            type: string
                            format: uuid
                          address:
                            type:
                              - string
                              - "null"
                            description: Provisioned managed inbox FQDN, or null if the inbox publish was deferred.
                          plan:
                            type: string
                            enum:
                              - agent
                          limits:
                            type: object
                            description: Plan-derived quota limits for an account.
                            properties:
                              storage_mb:
                                type: number
                              send_per_hour:
                                type: number
                              send_per_day:
                                type: number
                              api_per_minute:
                                type: number
                              webhooks_max_global:
                                type:
                                  - number
                                  - "null"
                              webhooks_per_domain:
                                type: boolean
                              filters_per_domain:
                                type: boolean
                              spam_thresholds_per_domain:
                                type: boolean
                            required:
                              - storage_mb
                              - send_per_hour
                              - send_per_day
                              - api_per_minute
                              - webhooks_max_global
                              - webhooks_per_domain
                              - filters_per_domain
                              - spam_thresholds_per_domain
                          upgrade:
                            type: object
                            description: In-band pointer to the upgrade path for an agent account.
                            properties:
                              plan:
                                type: string
                                enum:
                                  - developer
                              description:
                                type: string
                              claim_path:
                                type: string
                            required:
                              - plan
                              - description
                              - claim_path
                        required:
                          - api_key
                          - org_id
                          - address
                          - plan
                          - limits
                          - upgrade
        "400":
          $ref: "#/components/responses/ValidationError"
          description: Invalid request parameters
        "429":
          $ref: "#/components/responses/RateLimited"
          description: Rate limit exceeded
      parameters:
        - name: Idempotency-Key
          in: header
          required: false
          description: Optional client-supplied idempotency key. Retrying a request with the same key returns the original result instead of performing the action a second time; if omitted the server derives one from the canonical payload hash. Safe to retry network failures without duplicating side effects.
          schema:
            type: string
            minLength: 1
            maxLength: 255
  /agent/claim/start:
    post:
      operationId: startAgentClaim
      summary: Start an agent account email claim
      description: |
        Begins upgrading an emailless `agent` account into a full `developer`
        account by confirming an email address. Authenticated by the agent's own
        API key (the org is taken from the credential). Sends a verification
        code to the supplied email and returns the claim session id plus resend
        timing. Submit the code to `/agent/claim/verify` to complete the
        upgrade. Confirming an email that already belongs to a Primitive account
        is rejected.
      tags:
        - Agent
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              additionalProperties: false
              properties:
                email:
                  type: string
                  format: email
                  maxLength: 254
                  description: Email to confirm. Must not already belong to a Primitive account.
              required:
                - email
      responses:
        "200":
          description: Claim started and verification email sent
          headers:
            ratelimit-limit:
              description: Maximum number of requests allowed in the current window.
              schema:
                type: integer
                minimum: 1
                example: 120
            ratelimit-remaining:
              description: Remaining requests in the current window.
              schema:
                type: integer
                minimum: 0
                example: 118
            ratelimit-reset:
              description: Unix timestamp (seconds) when the current window resets.
              schema:
                type: integer
                example: 1700000060
            ratelimit-policy:
              description: Rate-limit policy in `limit;w=seconds` format, e.g. `120;w=60`.
              schema:
                type: string
                example: 120;w=60
            Cache-Control:
              schema:
                type: string
              description: Always `no-store`
          content:
            application/json:
              schema:
                allOf:
                  - type: object
                    properties:
                      success:
                        type: boolean
                        const: true
                    required:
                      - success
                      - data
                  - type: object
                    properties:
                      data:
                        type: object
                        properties:
                          claim_session_id:
                            type: string
                          resend_after_seconds:
                            type: integer
                          expires_in_seconds:
                            type: integer
                        required:
                          - claim_session_id
                          - resend_after_seconds
                          - expires_in_seconds
        "400":
          $ref: "#/components/responses/ValidationError"
          description: Invalid request parameters
        "401":
          $ref: "#/components/responses/Unauthorized"
          description: Invalid or missing API key
        "404":
          $ref: "#/components/responses/NotFound"
          description: Resource not found
        "409":
          $ref: "#/components/responses/Conflict"
          description: The email is already in use, or the account is not claimable
        "429":
          $ref: "#/components/responses/RateLimited"
          description: Rate limit exceeded
      security:
        - BearerAuth: []
      parameters:
        - name: Idempotency-Key
          in: header
          required: false
          description: Optional client-supplied idempotency key. Retrying a request with the same key returns the original result instead of performing the action a second time; if omitted the server derives one from the canonical payload hash. Safe to retry network failures without duplicating side effects.
          schema:
            type: string
            minLength: 1
            maxLength: 255
  /agent/claim/verify:
    post:
      operationId: verifyAgentClaim
      summary: Verify an agent account email claim
      description: |
        Confirms the verification code emailed by `/agent/claim/start` and
        upgrades the account to the `developer` plan. The org id, API key, and
        managed inbox all carry over; the send cap lifts. Authenticated by the
        agent's own API key.
      tags:
        - Agent
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              additionalProperties: false
              properties:
                verification_code:
                  type: string
                  minLength: 1
                  maxLength: 32
                  description: The verification code emailed by the claim start step.
              required:
                - verification_code
      responses:
        "200":
          description: Claim verified; account upgraded to developer
          headers:
            ratelimit-limit:
              description: Maximum number of requests allowed in the current window.
              schema:
                type: integer
                minimum: 1
                example: 120
            ratelimit-remaining:
              description: Remaining requests in the current window.
              schema:
                type: integer
                minimum: 0
                example: 118
            ratelimit-reset:
              description: Unix timestamp (seconds) when the current window resets.
              schema:
                type: integer
                example: 1700000060
            ratelimit-policy:
              description: Rate-limit policy in `limit;w=seconds` format, e.g. `120;w=60`.
              schema:
                type: string
                example: 120;w=60
            Cache-Control:
              schema:
                type: string
              description: Always `no-store`
          content:
            application/json:
              schema:
                allOf:
                  - type: object
                    properties:
                      success:
                        type: boolean
                        const: true
                    required:
                      - success
                      - data
                  - type: object
                    properties:
                      data:
                        type: object
                        properties:
                          org_id:
                            type: string
                            format: uuid
                          plan:
                            type: string
                            enum:
                              - developer
                          email:
                            type: string
                            format: email
                          limits:
                            type: object
                            description: Plan-derived quota limits for an account.
                            properties:
                              storage_mb:
                                type: number
                              send_per_hour:
                                type: number
                              send_per_day:
                                type: number
                              api_per_minute:
                                type: number
                              webhooks_max_global:
                                type:
                                  - number
                                  - "null"
                              webhooks_per_domain:
                                type: boolean
                              filters_per_domain:
                                type: boolean
                              spam_thresholds_per_domain:
                                type: boolean
                            required:
                              - storage_mb
                              - send_per_hour
                              - send_per_day
                              - api_per_minute
                              - webhooks_max_global
                              - webhooks_per_domain
                              - filters_per_domain
                              - spam_thresholds_per_domain
                        required:
                          - org_id
                          - plan
                          - email
                          - limits
        "400":
          $ref: "#/components/responses/ValidationError"
          description: Invalid request parameters
        "401":
          $ref: "#/components/responses/Unauthorized"
          description: Invalid or missing API key
        "404":
          $ref: "#/components/responses/NotFound"
          description: Resource not found
        "409":
          $ref: "#/components/responses/Conflict"
          description: The account is already claimed, or the email is in use
        "410":
          $ref: "#/components/responses/Gone"
          description: The claim or its verification code has expired
        "429":
          $ref: "#/components/responses/RateLimited"
          description: Rate limit exceeded
      security:
        - BearerAuth: []
      parameters:
        - name: Idempotency-Key
          in: header
          required: false
          description: Optional client-supplied idempotency key. Retrying a request with the same key returns the original result instead of performing the action a second time; if omitted the server derives one from the canonical payload hash. Safe to retry network failures without duplicating side effects.
          schema:
            type: string
            minLength: 1
            maxLength: 255
  /agent/claim/link:
    post:
      operationId: createAgentClaimLink
      summary: Create a browser claim link
      description: |
        Mints an opaque, single-use link an agent can hand to a human to
        complete the email-confirmation upgrade in a browser. Authenticated by
        the agent's own API key. `claim_url` is null when the API host cannot
        resolve a web origin to build the link.
      tags:
        - Agent
      requestBody:
        required: false
        content:
          application/json:
            schema:
              type: object
              additionalProperties: false
              description: No fields; an empty object is accepted.
              properties: {}
      responses:
        "200":
          description: Claim link created
          headers:
            ratelimit-limit:
              description: Maximum number of requests allowed in the current window.
              schema:
                type: integer
                minimum: 1
                example: 120
            ratelimit-remaining:
              description: Remaining requests in the current window.
              schema:
                type: integer
                minimum: 0
                example: 118
            ratelimit-reset:
              description: Unix timestamp (seconds) when the current window resets.
              schema:
                type: integer
                example: 1700000060
            ratelimit-policy:
              description: Rate-limit policy in `limit;w=seconds` format, e.g. `120;w=60`.
              schema:
                type: string
                example: 120;w=60
            Cache-Control:
              schema:
                type: string
              description: Always `no-store`
          content:
            application/json:
              schema:
                allOf:
                  - type: object
                    properties:
                      success:
                        type: boolean
                        const: true
                    required:
                      - success
                      - data
                  - type: object
                    properties:
                      data:
                        type: object
                        properties:
                          claim_token:
                            type: string
                          claim_url:
                            type:
                              - string
                              - "null"
                            description: Browser URL to hand to a human, or null if no web origin is configured.
                          expires_in_seconds:
                            type: integer
                        required:
                          - claim_token
                          - claim_url
                          - expires_in_seconds
        "401":
          $ref: "#/components/responses/Unauthorized"
          description: Invalid or missing API key
        "404":
          $ref: "#/components/responses/NotFound"
          description: Resource not found
        "409":
          $ref: "#/components/responses/Conflict"
          description: The account is not claimable (not an agent account, or already claimed)
        "429":
          $ref: "#/components/responses/RateLimited"
          description: Rate limit exceeded
      security:
        - BearerAuth: []
      parameters:
        - name: Idempotency-Key
          in: header
          required: false
          description: Optional client-supplied idempotency key. Retrying a request with the same key returns the original result instead of performing the action a second time; if omitted the server derives one from the canonical payload hash. Safe to retry network failures without duplicating side effects.
          schema:
            type: string
            minLength: 1
            maxLength: 255
  /cli/logout:
    post:
      operationId: cliLogout
      summary: Revoke the current CLI OAuth session
      description: |
        Revokes the OAuth grant used to authenticate the request. API-key
        authenticated legacy logout requests succeed without deleting server API
        keys so old local CLI state can be cleared safely.
      tags:
        - CLI
      requestBody:
        required: false
        content:
          application/json:
            schema:
              type: object
              additionalProperties: false
              properties:
                key_id:
                  type: string
                  format: uuid
                  description: Optional id guard; when provided it must match the authenticated OAuth grant id or API key id
      responses:
        "200":
          description: CLI logout completed
          content:
            application/json:
              schema:
                allOf:
                  - type: object
                    properties:
                      success:
                        type: boolean
                        const: true
                    required:
                      - success
                      - data
                  - type: object
                    properties:
                      data:
                        type: object
                        properties:
                          revoked:
                            type: boolean
                            description: True when an OAuth grant was revoked. False for API-key-authenticated legacy logout, which only clears local CLI state.
                          key_id:
                            type: string
                            format: uuid
                            description: API key id for API-key-authenticated legacy logout
                          oauth_grant_id:
                            type: string
                            format: uuid
                            description: OAuth grant id revoked by OAuth-authenticated logout
                        required:
                          - revoked
          headers:
            ratelimit-limit:
              description: Maximum number of requests allowed in the current window.
              schema:
                type: integer
                minimum: 1
                example: 120
            ratelimit-remaining:
              description: Remaining requests in the current window.
              schema:
                type: integer
                minimum: 0
                example: 118
            ratelimit-reset:
              description: Unix timestamp (seconds) when the current window resets.
              schema:
                type: integer
                example: 1700000060
            ratelimit-policy:
              description: Rate-limit policy in `limit;w=seconds` format, e.g. `120;w=60`.
              schema:
                type: string
                example: 120;w=60
        "400":
          $ref: "#/components/responses/ValidationError"
          description: Invalid request parameters
        "401":
          $ref: "#/components/responses/Unauthorized"
          description: Invalid or missing API key
        "403":
          $ref: "#/components/responses/Forbidden"
          description: Authenticated caller lacks permission for the operation
        "404":
          $ref: "#/components/responses/NotFound"
          description: Resource not found
      security:
        - BearerAuth: []
      parameters:
        - name: Idempotency-Key
          in: header
          required: false
          description: Optional client-supplied idempotency key. Retrying a request with the same key returns the original result instead of performing the action a second time; if omitted the server derives one from the canonical payload hash. Safe to retry network failures without duplicating side effects.
          schema:
            type: string
            minLength: 1
            maxLength: 255
  /account:
    get:
      operationId: getAccount
      summary: Get account info
      tags:
        - Account
      responses:
        "200":
          description: Account details
          content:
            application/json:
              schema:
                allOf:
                  - type: object
                    properties:
                      success:
                        type: boolean
                        const: true
                    required:
                      - success
                      - data
                  - type: object
                    properties:
                      data:
                        type: object
                        properties:
                          id:
                            type: string
                            format: uuid
                          email:
                            type: string
                          plan:
                            type: string
                          limits:
                            type: object
                            description: Plan-derived quota limits for an account.
                            properties:
                              storage_mb:
                                type: number
                              send_per_hour:
                                type: number
                              send_per_day:
                                type: number
                              api_per_minute:
                                type: number
                              webhooks_max_global:
                                type:
                                  - number
                                  - "null"
                              webhooks_per_domain:
                                type: boolean
                              filters_per_domain:
                                type: boolean
                              spam_thresholds_per_domain:
                                type: boolean
                            required:
                              - storage_mb
                              - send_per_hour
                              - send_per_day
                              - api_per_minute
                              - webhooks_max_global
                              - webhooks_per_domain
                              - filters_per_domain
                              - spam_thresholds_per_domain
                          entitlements:
                            type: array
                            items:
                              type: string
                            description: |
                              Granted org entitlement keys (sorted). A headless caller reads its
                              capabilities here — e.g. an emailless agent seeing only
                              ["send_mail", "send_to_known_addresses"] knows it is reply-only.
                          managed_inbox_address:
                            type:
                              - string
                              - "null"
                            description: The managed inbox FQDN to reply as, or null if the org has no managed inbox.
                          created_at:
                            type: string
                            format: date-time
                          onboarding_completed:
                            type: boolean
                          onboarding_step:
                            type:
                              - string
                              - "null"
                          stripe_subscription_status:
                            type:
                              - string
                              - "null"
                          subscription_current_period_end:
                            type:
                              - string
                              - "null"
                            format: date-time
                          subscription_cancel_at_period_end:
                            type:
                              - boolean
                              - "null"
                          spam_threshold:
                            type:
                              - number
                              - "null"
                            minimum: 0
                            maximum: 15
                          discard_content_on_webhook_confirmed:
                            type: boolean
                          webhook_secret_rotated_at:
                            type:
                              - string
                              - "null"
                            format: date-time
                        required:
                          - id
                          - email
                          - plan
                          - limits
                          - entitlements
                          - managed_inbox_address
                          - created_at
                          - discard_content_on_webhook_confirmed
          headers:
            ratelimit-limit:
              description: Maximum number of requests allowed in the current window.
              schema:
                type: integer
                minimum: 1
                example: 120
            ratelimit-remaining:
              description: Remaining requests in the current window.
              schema:
                type: integer
                minimum: 0
                example: 118
            ratelimit-reset:
              description: Unix timestamp (seconds) when the current window resets.
              schema:
                type: integer
                example: 1700000060
            ratelimit-policy:
              description: Rate-limit policy in `limit;w=seconds` format, e.g. `120;w=60`.
              schema:
                type: string
                example: 120;w=60
        "401":
          $ref: "#/components/responses/Unauthorized"
          description: Invalid or missing API key
        "404":
          $ref: "#/components/responses/NotFound"
          description: Resource not found
      security:
        - BearerAuth: []
      description: "Retrieve the authenticated organization's account: organization identity, current plan, usage, and onboarding state."
    patch:
      operationId: updateAccount
      summary: Update account settings
      tags:
        - Account
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              additionalProperties: false
              properties:
                spam_threshold:
                  type:
                    - number
                    - "null"
                  minimum: 0
                  maximum: 15
                  description: Global spam score threshold (0-15). Emails scoring above this are rejected. Set to null to disable.
                discard_content_on_webhook_confirmed:
                  type: boolean
                  description: Whether to discard email content after the webhook endpoint confirms receipt.
              minProperties: 1
      responses:
        "200":
          description: Updated account
          content:
            application/json:
              schema:
                allOf:
                  - type: object
                    properties:
                      success:
                        type: boolean
                        const: true
                    required:
                      - success
                      - data
                  - type: object
                    properties:
                      data:
                        type: object
                        properties:
                          id:
                            type: string
                            format: uuid
                          email:
                            type: string
                          plan:
                            type: string
                          spam_threshold:
                            type:
                              - number
                              - "null"
                            minimum: 0
                            maximum: 15
                          discard_content_on_webhook_confirmed:
                            type: boolean
                        required:
                          - id
                          - email
                          - plan
                          - discard_content_on_webhook_confirmed
          headers:
            ratelimit-limit:
              description: Maximum number of requests allowed in the current window.
              schema:
                type: integer
                minimum: 1
                example: 120
            ratelimit-remaining:
              description: Remaining requests in the current window.
              schema:
                type: integer
                minimum: 0
                example: 118
            ratelimit-reset:
              description: Unix timestamp (seconds) when the current window resets.
              schema:
                type: integer
                example: 1700000060
            ratelimit-policy:
              description: Rate-limit policy in `limit;w=seconds` format, e.g. `120;w=60`.
              schema:
                type: string
                example: 120;w=60
        "400":
          $ref: "#/components/responses/ValidationError"
          description: Invalid request parameters
        "401":
          $ref: "#/components/responses/Unauthorized"
          description: Invalid or missing API key
        "404":
          $ref: "#/components/responses/NotFound"
          description: Resource not found
      security:
        - BearerAuth: []
      description: Update the mutable account settings (such as display name and notification preferences) for the authenticated organization.
  /account/storage:
    get:
      operationId: getStorageStats
      summary: Get storage usage
      tags:
        - Account
      responses:
        "200":
          description: Storage statistics
          content:
            application/json:
              schema:
                allOf:
                  - type: object
                    properties:
                      success:
                        type: boolean
                        const: true
                    required:
                      - success
                      - data
                  - type: object
                    properties:
                      data:
                        type: object
                        properties:
                          used_bytes:
                            type: integer
                            description: Total storage used in bytes
                          used_kb:
                            type: number
                            description: Total storage used in kilobytes (1 decimal)
                          used_mb:
                            type: number
                            description: Total storage used in megabytes (2 decimals)
                          quota_mb:
                            type: number
                            description: Storage quota in megabytes (based on plan)
                          percentage:
                            type: number
                            description: Percentage of quota used (1 decimal)
                          emails_count:
                            type: integer
                            description: Number of stored emails
                        required:
                          - used_bytes
                          - used_kb
                          - used_mb
                          - quota_mb
                          - percentage
                          - emails_count
          headers:
            ratelimit-limit:
              description: Maximum number of requests allowed in the current window.
              schema:
                type: integer
                minimum: 1
                example: 120
            ratelimit-remaining:
              description: Remaining requests in the current window.
              schema:
                type: integer
                minimum: 0
                example: 118
            ratelimit-reset:
              description: Unix timestamp (seconds) when the current window resets.
              schema:
                type: integer
                example: 1700000060
            ratelimit-policy:
              description: Rate-limit policy in `limit;w=seconds` format, e.g. `120;w=60`.
              schema:
                type: string
                example: 120;w=60
        "401":
          $ref: "#/components/responses/Unauthorized"
          description: Invalid or missing API key
        "404":
          $ref: "#/components/responses/NotFound"
          description: Resource not found
      security:
        - BearerAuth: []
      description: Return stored-email storage usage and the remaining quota for the authenticated organization.
  /account/webhook-secret:
    get:
      operationId: getWebhookSecret
      summary: Get webhook signing secret
      description: |
        Returns the webhook signing secret for your account. If no
        secret exists yet, one is generated automatically on first
        access.

        Signing is account-scoped, not per-endpoint. Every webhook
        delivery from any of your registered endpoints is signed
        with this single secret. Rotate via
        `POST /account/webhook-secret/rotate`.

        **Secret format**: the returned string looks base64-shaped
        (e.g. `XNHBBW8VqoBjRfNs1tkZj11jTk...`) but is NOT base64.
        Use it AS-IS as a UTF-8 string when computing HMAC over a
        delivery body. Base64-decoding before HMAC will silently
        produce mismatched signatures.

        See the API-level "Webhook signing" section for the full
        wire format (header name, signed string shape, hash algo,
        tolerance) including a language-agnostic verification
        recipe.
      tags:
        - Account
      responses:
        "200":
          description: Webhook secret
          content:
            application/json:
              schema:
                allOf:
                  - type: object
                    properties:
                      success:
                        type: boolean
                        const: true
                    required:
                      - success
                      - data
                  - type: object
                    properties:
                      data:
                        type: object
                        properties:
                          secret:
                            type: string
                            description: The webhook signing secret value
                        required:
                          - secret
          headers:
            ratelimit-limit:
              description: Maximum number of requests allowed in the current window.
              schema:
                type: integer
                minimum: 1
                example: 120
            ratelimit-remaining:
              description: Remaining requests in the current window.
              schema:
                type: integer
                minimum: 0
                example: 118
            ratelimit-reset:
              description: Unix timestamp (seconds) when the current window resets.
              schema:
                type: integer
                example: 1700000060
            ratelimit-policy:
              description: Rate-limit policy in `limit;w=seconds` format, e.g. `120;w=60`.
              schema:
                type: string
                example: 120;w=60
        "401":
          $ref: "#/components/responses/Unauthorized"
          description: Invalid or missing API key
        "404":
          $ref: "#/components/responses/NotFound"
          description: Resource not found
      security:
        - BearerAuth: []
  /account/webhook-secret/rotate:
    post:
      operationId: rotateWebhookSecret
      summary: Rotate webhook signing secret
      description: |
        Generates a new webhook signing secret, replacing the current one.
        Rate limited to once per 60 minutes.
      tags:
        - Account
      responses:
        "200":
          description: New webhook secret
          content:
            application/json:
              schema:
                allOf:
                  - type: object
                    properties:
                      success:
                        type: boolean
                        const: true
                    required:
                      - success
                      - data
                  - type: object
                    properties:
                      data:
                        type: object
                        properties:
                          secret:
                            type: string
                            description: The webhook signing secret value
                        required:
                          - secret
          headers:
            ratelimit-limit:
              description: Maximum number of requests allowed in the current window.
              schema:
                type: integer
                minimum: 1
                example: 120
            ratelimit-remaining:
              description: Remaining requests in the current window.
              schema:
                type: integer
                minimum: 0
                example: 118
            ratelimit-reset:
              description: Unix timestamp (seconds) when the current window resets.
              schema:
                type: integer
                example: 1700000060
            ratelimit-policy:
              description: Rate-limit policy in `limit;w=seconds` format, e.g. `120;w=60`.
              schema:
                type: string
                example: 120;w=60
        "400":
          $ref: "#/components/responses/ValidationError"
          description: Invalid request parameters
        "401":
          $ref: "#/components/responses/Unauthorized"
          description: Invalid or missing API key
        "404":
          $ref: "#/components/responses/NotFound"
          description: Resource not found
        "429":
          $ref: "#/components/responses/RateLimited"
          description: Rate limit exceeded
      security:
        - BearerAuth: []
      parameters:
        - name: Idempotency-Key
          in: header
          required: false
          description: Optional client-supplied idempotency key. Retrying a request with the same key returns the original result instead of performing the action a second time; if omitted the server derives one from the canonical payload hash. Safe to retry network failures without duplicating side effects.
          schema:
            type: string
            minLength: 1
            maxLength: 255
  /domains:
    get:
      operationId: listDomains
      summary: List all domains
      description: |
        Returns all verified and unverified domains for your organization,
        sorted by creation date (newest first). Each domain includes a
        `verified` boolean to distinguish between the two states.
      tags:
        - Domains
      responses:
        "200":
          description: List of domains
          content:
            application/json:
              schema:
                allOf:
                  - type: object
                    properties:
                      success:
                        type: boolean
                        const: true
                    required:
                      - success
                      - data
                  - type: object
                    properties:
                      data:
                        type: array
                        items:
                          description: |
                            A domain can be either verified or unverified. Verified domains have
                            `is_active` and `spam_threshold` fields. Unverified domains have a
                            `verification_token` and `dns_records` for DNS setup.
                          oneOf:
                            - type: object
                              properties:
                                id:
                                  type: string
                                  format: uuid
                                org_id:
                                  type: string
                                  format: uuid
                                domain:
                                  type: string
                                verified:
                                  type: boolean
                                  const: true
                                is_active:
                                  type: boolean
                                spam_threshold:
                                  type:
                                    - number
                                    - "null"
                                  minimum: 0
                                  maximum: 15
                                verification_token:
                                  type:
                                    - string
                                    - "null"
                                created_at:
                                  type: string
                                  format: date-time
                              required:
                                - id
                                - org_id
                                - domain
                                - verified
                                - is_active
                                - created_at
                            - type: object
                              properties:
                                id:
                                  type: string
                                  format: uuid
                                org_id:
                                  type: string
                                  format: uuid
                                domain:
                                  type: string
                                verified:
                                  type: boolean
                                  const: false
                                verification_token:
                                  type: string
                                  description: Add this value as a TXT record to verify ownership
                                dns_records:
                                  type: array
                                  description: Exact DNS records to publish for a pending domain claim or verification attempt.
                                  items:
                                    type: object
                                    additionalProperties: false
                                    properties:
                                      type:
                                        type: string
                                        enum:
                                          - MX
                                          - TXT
                                        description: DNS record type.
                                      name:
                                        type: string
                                        description: DNS-provider host/name value relative to the managed root zone.
                                      fqdn:
                                        type: string
                                        description: Fully-qualified DNS record name.
                                      value:
                                        type: string
                                        description: Exact value to publish.
                                      priority:
                                        type: integer
                                        description: MX priority. Present only for MX records.
                                      ttl:
                                        type: integer
                                        description: Suggested TTL in seconds when the API can provide one.
                                      required:
                                        type: boolean
                                        const: true
                                      purpose:
                                        type: string
                                        enum:
                                          - inbound_mx
                                          - ownership_verification
                                          - spf
                                          - dkim
                                          - dmarc
                                          - tls_reporting
                                      status:
                                        type: string
                                        enum:
                                          - pending
                                          - found
                                          - missing
                                          - incorrect
                                      message:
                                        type: string
                                        description: Short explanation of why this record is needed.
                                    required:
                                      - type
                                      - name
                                      - fqdn
                                      - value
                                      - required
                                      - purpose
                                      - status
                                created_at:
                                  type: string
                                  format: date-time
                              required:
                                - id
                                - org_id
                                - domain
                                - verified
                                - verification_token
                                - created_at
          headers:
            ratelimit-limit:
              description: Maximum number of requests allowed in the current window.
              schema:
                type: integer
                minimum: 1
                example: 120
            ratelimit-remaining:
              description: Remaining requests in the current window.
              schema:
                type: integer
                minimum: 0
                example: 118
            ratelimit-reset:
              description: Unix timestamp (seconds) when the current window resets.
              schema:
                type: integer
                example: 1700000060
            ratelimit-policy:
              description: Rate-limit policy in `limit;w=seconds` format, e.g. `120;w=60`.
              schema:
                type: string
                example: 120;w=60
        "401":
          $ref: "#/components/responses/Unauthorized"
          description: Invalid or missing API key
      security:
        - BearerAuth: []
    post:
      operationId: addDomain
      summary: Claim a new domain
      description: |
        Creates an unverified domain claim and returns the exact
        DNS records to publish in `dns_records`. Publish those
        records before calling the verify endpoint. To give users
        an importable DNS file, call `downloadDomainZoneFile` or run
        `primitive domains zone-file --id <domain-id>`.
      tags:
        - Domains
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              additionalProperties: false
              properties:
                domain:
                  type: string
                  minLength: 1
                  maxLength: 253
                  description: The domain name to claim (e.g. "example.com")
                confirmed:
                  type: boolean
                  description: Set to true to confirm replacing an existing mailbox provider after an mx_conflict response.
                outbound:
                  type: boolean
                  deprecated: true
                  description: Deprecated and ignored. Outbound DNS is provisioned for every new domain claim.
              required:
                - domain
      responses:
        "201":
          description: Domain claim created
          content:
            application/json:
              schema:
                allOf:
                  - type: object
                    properties:
                      success:
                        type: boolean
                        const: true
                    required:
                      - success
                      - data
                  - type: object
                    properties:
                      data:
                        type: object
                        properties:
                          id:
                            type: string
                            format: uuid
                          org_id:
                            type: string
                            format: uuid
                          domain:
                            type: string
                          verified:
                            type: boolean
                            const: false
                          verification_token:
                            type: string
                            description: Add this value as a TXT record to verify ownership
                          dns_records:
                            type: array
                            description: Exact DNS records to publish for a pending domain claim or verification attempt.
                            items:
                              type: object
                              additionalProperties: false
                              properties:
                                type:
                                  type: string
                                  enum:
                                    - MX
                                    - TXT
                                  description: DNS record type.
                                name:
                                  type: string
                                  description: DNS-provider host/name value relative to the managed root zone.
                                fqdn:
                                  type: string
                                  description: Fully-qualified DNS record name.
                                value:
                                  type: string
                                  description: Exact value to publish.
                                priority:
                                  type: integer
                                  description: MX priority. Present only for MX records.
                                ttl:
                                  type: integer
                                  description: Suggested TTL in seconds when the API can provide one.
                                required:
                                  type: boolean
                                  const: true
                                purpose:
                                  type: string
                                  enum:
                                    - inbound_mx
                                    - ownership_verification
                                    - spf
                                    - dkim
                                    - dmarc
                                    - tls_reporting
                                status:
                                  type: string
                                  enum:
                                    - pending
                                    - found
                                    - missing
                                    - incorrect
                                message:
                                  type: string
                                  description: Short explanation of why this record is needed.
                              required:
                                - type
                                - name
                                - fqdn
                                - value
                                - required
                                - purpose
                                - status
                          created_at:
                            type: string
                            format: date-time
                        required:
                          - id
                          - org_id
                          - domain
                          - verified
                          - verification_token
                          - created_at
        "400":
          $ref: "#/components/responses/ValidationError"
          description: Invalid request parameters
        "401":
          $ref: "#/components/responses/Unauthorized"
          description: Invalid or missing API key
        "409":
          $ref: "#/components/responses/Conflict"
          description: |
            Domain claim conflicts with existing state. Two error codes
            are possible:
              * `mx_conflict`: the domain's current MX records point at
                another mailbox provider. The response includes
                `error.details.mx_conflict` with the detected provider
                and a suggested subdomain.
              * `conflict`: the domain is already claimed by another
                org, or a pending claim exists for another user.
      security:
        - BearerAuth: []
      parameters:
        - name: Idempotency-Key
          in: header
          required: false
          description: Optional client-supplied idempotency key. Retrying a request with the same key returns the original result instead of performing the action a second time; if omitted the server derives one from the canonical payload hash. Safe to retry network failures without duplicating side effects.
          schema:
            type: string
            minLength: 1
            maxLength: 255
  /domains/{id}:
    parameters:
      - name: id
        in: path
        required: true
        schema:
          type: string
          format: uuid
        description: Resource UUID
    patch:
      operationId: updateDomain
      summary: Update domain settings
      description: |
        Update a verified domain's settings. Only verified domains can be
        updated. Per-domain spam thresholds require a Pro plan.
      tags:
        - Domains
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              additionalProperties: false
              properties:
                is_active:
                  type: boolean
                  description: Whether the domain accepts incoming emails
                spam_threshold:
                  type:
                    - number
                    - "null"
                  minimum: 0
                  maximum: 15
                  description: Per-domain spam threshold override (Pro plan required)
              minProperties: 1
      responses:
        "200":
          description: Updated domain
          content:
            application/json:
              schema:
                allOf:
                  - type: object
                    properties:
                      success:
                        type: boolean
                        const: true
                    required:
                      - success
                      - data
                  - type: object
                    properties:
                      data:
                        type: object
                        properties:
                          id:
                            type: string
                            format: uuid
                          org_id:
                            type: string
                            format: uuid
                          domain:
                            type: string
                          verified:
                            type: boolean
                            const: true
                          is_active:
                            type: boolean
                          spam_threshold:
                            type:
                              - number
                              - "null"
                            minimum: 0
                            maximum: 15
                          verification_token:
                            type:
                              - string
                              - "null"
                          created_at:
                            type: string
                            format: date-time
                        required:
                          - id
                          - org_id
                          - domain
                          - verified
                          - is_active
                          - created_at
          headers:
            ratelimit-limit:
              description: Maximum number of requests allowed in the current window.
              schema:
                type: integer
                minimum: 1
                example: 120
            ratelimit-remaining:
              description: Remaining requests in the current window.
              schema:
                type: integer
                minimum: 0
                example: 118
            ratelimit-reset:
              description: Unix timestamp (seconds) when the current window resets.
              schema:
                type: integer
                example: 1700000060
            ratelimit-policy:
              description: Rate-limit policy in `limit;w=seconds` format, e.g. `120;w=60`.
              schema:
                type: string
                example: 120;w=60
        "400":
          $ref: "#/components/responses/ValidationError"
          description: Invalid request parameters
        "401":
          $ref: "#/components/responses/Unauthorized"
          description: Invalid or missing API key
        "404":
          $ref: "#/components/responses/NotFound"
          description: Resource not found
      security:
        - BearerAuth: []
    delete:
      operationId: deleteDomain
      summary: Delete a domain
      description: Remove a domain from the organization. Inbound mail for its addresses stops being accepted.
      tags:
        - Domains
      responses:
        "200":
          description: Resource deleted
          content:
            application/json:
              schema:
                allOf:
                  - type: object
                    properties:
                      success:
                        type: boolean
                        const: true
                    required:
                      - success
                      - data
                  - type: object
                    properties:
                      data:
                        type: object
                        properties:
                          deleted:
                            type: boolean
                            const: true
                        required:
                          - deleted
          headers:
            ratelimit-limit:
              description: Maximum number of requests allowed in the current window.
              schema:
                type: integer
                minimum: 1
                example: 120
            ratelimit-remaining:
              description: Remaining requests in the current window.
              schema:
                type: integer
                minimum: 0
                example: 118
            ratelimit-reset:
              description: Unix timestamp (seconds) when the current window resets.
              schema:
                type: integer
                example: 1700000060
            ratelimit-policy:
              description: Rate-limit policy in `limit;w=seconds` format, e.g. `120;w=60`.
              schema:
                type: string
                example: 120;w=60
        "400":
          $ref: "#/components/responses/ValidationError"
          description: Invalid request parameters
        "401":
          $ref: "#/components/responses/Unauthorized"
          description: Invalid or missing API key
        "404":
          $ref: "#/components/responses/NotFound"
          description: Resource not found
      security:
        - BearerAuth: []
  /domains/{id}/verify:
    parameters:
      - name: id
        in: path
        required: true
        schema:
          type: string
          format: uuid
        description: Resource UUID
    post:
      operationId: verifyDomain
      summary: Verify domain ownership
      description: |
        Checks DNS records required for inbound routing, ownership,
        and outbound authentication: MX, ownership TXT, SPF, DKIM,
        DMARC, and TLS-RPT.
        On success, the domain is promoted from unverified to verified.
        On failure, returns which checks passed and which failed,
        plus the exact DNS records still expected. To give users
        an importable DNS file for missing records, call
        `downloadDomainZoneFile` or run
        `primitive domains zone-file --id <domain-id>`.
      tags:
        - Domains
      responses:
        "200":
          description: Verification result
          content:
            application/json:
              schema:
                allOf:
                  - type: object
                    properties:
                      success:
                        type: boolean
                        const: true
                    required:
                      - success
                      - data
                  - type: object
                    properties:
                      data:
                        oneOf:
                          - type: object
                            properties:
                              verified:
                                type: boolean
                                const: true
                              dns_records:
                                type: array
                                description: Exact DNS records to publish for a pending domain claim or verification attempt.
                                items:
                                  type: object
                                  additionalProperties: false
                                  properties:
                                    type:
                                      type: string
                                      enum:
                                        - MX
                                        - TXT
                                      description: DNS record type.
                                    name:
                                      type: string
                                      description: DNS-provider host/name value relative to the managed root zone.
                                    fqdn:
                                      type: string
                                      description: Fully-qualified DNS record name.
                                    value:
                                      type: string
                                      description: Exact value to publish.
                                    priority:
                                      type: integer
                                      description: MX priority. Present only for MX records.
                                    ttl:
                                      type: integer
                                      description: Suggested TTL in seconds when the API can provide one.
                                    required:
                                      type: boolean
                                      const: true
                                    purpose:
                                      type: string
                                      enum:
                                        - inbound_mx
                                        - ownership_verification
                                        - spf
                                        - dkim
                                        - dmarc
                                        - tls_reporting
                                    status:
                                      type: string
                                      enum:
                                        - pending
                                        - found
                                        - missing
                                        - incorrect
                                    message:
                                      type: string
                                      description: Short explanation of why this record is needed.
                                  required:
                                    - type
                                    - name
                                    - fqdn
                                    - value
                                    - required
                                    - purpose
                                    - status
                            required:
                              - verified
                          - type: object
                            properties:
                              verified:
                                type: boolean
                                const: false
                              mxFound:
                                type: boolean
                                description: Whether MX records point to Primitive
                              txtFound:
                                type: boolean
                                description: Whether the TXT verification record was found
                              spfFound:
                                type: boolean
                                description: Whether the SPF record includes Primitive.
                              dkimFound:
                                type: boolean
                                description: Whether the DKIM public key record was found.
                              dmarcFound:
                                type: boolean
                                description: Whether the DMARC record was found.
                              tlsRptFound:
                                type: boolean
                                description: Whether the TLS-RPT record was found.
                              dns_records:
                                type: array
                                description: Exact DNS records to publish for a pending domain claim or verification attempt.
                                items:
                                  type: object
                                  additionalProperties: false
                                  properties:
                                    type:
                                      type: string
                                      enum:
                                        - MX
                                        - TXT
                                      description: DNS record type.
                                    name:
                                      type: string
                                      description: DNS-provider host/name value relative to the managed root zone.
                                    fqdn:
                                      type: string
                                      description: Fully-qualified DNS record name.
                                    value:
                                      type: string
                                      description: Exact value to publish.
                                    priority:
                                      type: integer
                                      description: MX priority. Present only for MX records.
                                    ttl:
                                      type: integer
                                      description: Suggested TTL in seconds when the API can provide one.
                                    required:
                                      type: boolean
                                      const: true
                                    purpose:
                                      type: string
                                      enum:
                                        - inbound_mx
                                        - ownership_verification
                                        - spf
                                        - dkim
                                        - dmarc
                                        - tls_reporting
                                    status:
                                      type: string
                                      enum:
                                        - pending
                                        - found
                                        - missing
                                        - incorrect
                                    message:
                                      type: string
                                      description: Short explanation of why this record is needed.
                                  required:
                                    - type
                                    - name
                                    - fqdn
                                    - value
                                    - required
                                    - purpose
                                    - status
                              error:
                                type: string
                                description: Human-readable verification failure reason
                            required:
                              - verified
                              - mxFound
                              - txtFound
                              - error
          headers:
            ratelimit-limit:
              description: Maximum number of requests allowed in the current window.
              schema:
                type: integer
                minimum: 1
                example: 120
            ratelimit-remaining:
              description: Remaining requests in the current window.
              schema:
                type: integer
                minimum: 0
                example: 118
            ratelimit-reset:
              description: Unix timestamp (seconds) when the current window resets.
              schema:
                type: integer
                example: 1700000060
            ratelimit-policy:
              description: Rate-limit policy in `limit;w=seconds` format, e.g. `120;w=60`.
              schema:
                type: string
                example: 120;w=60
        "400":
          $ref: "#/components/responses/ValidationError"
          description: Invalid request parameters
        "401":
          $ref: "#/components/responses/Unauthorized"
          description: Invalid or missing API key
        "404":
          $ref: "#/components/responses/NotFound"
          description: Resource not found
      security:
        - BearerAuth: []
      parameters:
        - name: Idempotency-Key
          in: header
          required: false
          description: Optional client-supplied idempotency key. Retrying a request with the same key returns the original result instead of performing the action a second time; if omitted the server derives one from the canonical payload hash. Safe to retry network failures without duplicating side effects.
          schema:
            type: string
            minLength: 1
            maxLength: 255
  /domains/{id}/zone-file:
    parameters:
      - name: id
        in: path
        required: true
        schema:
          type: string
          format: uuid
        description: Resource UUID
    get:
      operationId: downloadDomainZoneFile
      summary: Download domain DNS zone file
      description: Downloads a BIND-format DNS zone file containing the DNS records required for a domain claim. Agents should offer this after `addDomain` when users want to import DNS records instead of copying each record manually.
      tags:
        - Domains
      security:
        - BearerAuth: []
      parameters:
        - name: outbound_only
          in: query
          schema:
            type: boolean
          description: When true, include only outbound DNS records. Verified domains default to outbound-only; pending claims default to all required records.
      responses:
        "200":
          description: BIND-format zone file
          content:
            text/plain:
              schema:
                type: string
                format: binary
          headers:
            ratelimit-limit:
              description: Maximum number of requests allowed in the current window.
              schema:
                type: integer
                minimum: 1
                example: 120
            ratelimit-remaining:
              description: Remaining requests in the current window.
              schema:
                type: integer
                minimum: 0
                example: 118
            ratelimit-reset:
              description: Unix timestamp (seconds) when the current window resets.
              schema:
                type: integer
                example: 1700000060
            ratelimit-policy:
              description: Rate-limit policy in `limit;w=seconds` format, e.g. `120;w=60`.
              schema:
                type: string
                example: 120;w=60
            Content-Disposition:
              schema:
                type: string
                example: attachment; filename="example.com.zone"
        "400":
          $ref: "#/components/responses/ValidationError"
          description: Invalid request parameters
        "401":
          $ref: "#/components/responses/Unauthorized"
          description: Invalid or missing API key
        "404":
          $ref: "#/components/responses/NotFound"
          description: Resource not found
        "429":
          $ref: "#/components/responses/RateLimited"
          description: Rate limit exceeded
  /inbox/status:
    get:
      operationId: getInboxStatus
      summary: Get inbound inbox readiness
      description: Returns one consolidated view of domain verification, webhook/function processing routes, deployed functions, and recent inbound mail. Agents should use this before guiding users through inbound email setup.
      tags:
        - Inbox
      security:
        - BearerAuth: []
      responses:
        "200":
          description: Consolidated inbox readiness status
          content:
            application/json:
              schema:
                type: object
                additionalProperties: false
                properties:
                  ready:
                    type: boolean
                    description: True when an active inbound domain and at least one processing route are both ready.
                  receiving_ready:
                    type: boolean
                    description: True when at least one active verified or managed domain can receive mail.
                  processing_ready:
                    type: boolean
                    description: True when at least one receiving-ready domain has an enabled webhook or function route.
                  summary:
                    type: string
                  next_actions:
                    type: array
                    items:
                      type: object
                      additionalProperties: false
                      properties:
                        kind:
                          type: string
                          enum:
                            - add_domain
                            - verify_domain
                            - configure_processing
                            - send_test_email
                            - fix_failed_functions
                        message:
                          type: string
                          description: Human-readable next step.
                        command:
                          type: string
                          description: Suggested Primitive CLI command when there is an obvious next step.
                      required:
                        - kind
                        - message
                  domains:
                    type: array
                    items:
                      type: object
                      additionalProperties: false
                      properties:
                        id:
                          type: string
                        domain:
                          type: string
                        verified:
                          type: boolean
                        active:
                          type: boolean
                        managed:
                          type: boolean
                        receiving_ready:
                          type: boolean
                        processing_ready:
                          type: boolean
                        processing_route_count:
                          type: integer
                        endpoint_count:
                          type: integer
                        enabled_endpoint_count:
                          type: integer
                        function_endpoint_count:
                          type: integer
                        email_count:
                          type: integer
                          description: Number of inbound emails received for this domain in the last 30 days.
                        latest_email_received_at:
                          type:
                            - string
                            - "null"
                          format: date-time
                          description: Most recent inbound email received for this domain in the last 30 days.
                        status:
                          type: string
                          enum:
                            - ready
                            - stored_only
                            - pending_dns
                            - inactive
                      required:
                        - id
                        - domain
                        - verified
                        - active
                        - managed
                        - receiving_ready
                        - processing_ready
                        - processing_route_count
                        - endpoint_count
                        - enabled_endpoint_count
                        - function_endpoint_count
                        - email_count
                        - latest_email_received_at
                        - status
                  endpoints:
                    type: object
                    additionalProperties: false
                    properties:
                      total:
                        type: integer
                      enabled:
                        type: integer
                      disabled:
                        type: integer
                      fallback_enabled:
                        type: integer
                      domain_scoped_enabled:
                        type: integer
                      http_enabled:
                        type: integer
                      function_enabled:
                        type: integer
                    required:
                      - total
                      - enabled
                      - disabled
                      - fallback_enabled
                      - domain_scoped_enabled
                      - http_enabled
                      - function_enabled
                  functions:
                    type: object
                    additionalProperties: false
                    properties:
                      total:
                        type: integer
                      deployed:
                        type: integer
                      pending:
                        type: integer
                      failed:
                        type: integer
                    required:
                      - total
                      - deployed
                      - pending
                      - failed
                  recent_emails:
                    type: object
                    description: Inbound email activity from the last 30 days.
                    additionalProperties: false
                    properties:
                      total:
                        type: integer
                        description: Number of inbound emails received in the last 30 days.
                      latest_received_at:
                        type:
                          - string
                          - "null"
                        format: date-time
                        description: Most recent inbound email received in the last 30 days.
                    required:
                      - total
                      - latest_received_at
                required:
                  - ready
                  - receiving_ready
                  - processing_ready
                  - summary
                  - next_actions
                  - domains
                  - endpoints
                  - functions
                  - recent_emails
          headers:
            ratelimit-limit:
              description: Maximum number of requests allowed in the current window.
              schema:
                type: integer
                minimum: 1
                example: 120
            ratelimit-remaining:
              description: Remaining requests in the current window.
              schema:
                type: integer
                minimum: 0
                example: 118
            ratelimit-reset:
              description: Unix timestamp (seconds) when the current window resets.
              schema:
                type: integer
                example: 1700000060
            ratelimit-policy:
              description: Rate-limit policy in `limit;w=seconds` format, e.g. `120;w=60`.
              schema:
                type: string
                example: 120;w=60
        "401":
          $ref: "#/components/responses/Unauthorized"
          description: Invalid or missing API key
        "429":
          $ref: "#/components/responses/RateLimited"
          description: Rate limit exceeded
  /emails:
    get:
      operationId: listEmails
      summary: List inbound emails
      description: |
        Returns a paginated list of INBOUND emails received at your
        verified domains. Outbound messages sent via /send-mail are
        not included; this endpoint is the inbox view, not a
        unified send/receive history.

        Supports filtering by domain, status, date range, and
        free-text search across subject, sender, and recipient
        fields.

        For a compact text-table summary of the most recent N
        inbounds (no filters, no cursor pagination), the CLI ships
        `primitive emails:latest` as a one-line-per-email shortcut.
        It's TTY-aware so id columns are full UUIDs when piped, and
        a `--json` flag returns the same envelope this endpoint
        does. Use whichever fits the call site.
      tags:
        - Emails
      parameters:
        - name: cursor
          in: query
          schema:
            type: string
          description: |
            Pagination cursor from a previous response's `meta.cursor` field.
            Format: `{ISO-datetime}|{id}`
        - name: limit
          in: query
          schema:
            type: integer
            minimum: 1
            maximum: 100
            default: 50
          description: Number of results per page
        - name: domain_id
          in: query
          schema:
            type: string
            format: uuid
          description: Filter by domain ID
        - name: status
          in: query
          schema:
            type: string
            description: |
              Lifecycle status of an INBOUND email (a row in the `emails`
              table). Distinct from `SentEmailStatus`, which describes
              the OUTBOUND lifecycle (the `sent_emails` table) and uses
              a different vocabulary because the lifecycles differ.
              Possible values:

                - `pending`: the row was inserted at ingestion (mx_main)
                  and has not yet completed the spam / filter / auth
                  pipeline. Body and parsed fields are present; webhook
                  delivery is not yet scheduled. Most rows transition out
                  of `pending` within seconds.
                - `accepted`: the inbound passed the policy gates and is
                  queued for webhook delivery. The `webhook_status` field
                  tracks the separate webhook-delivery lifecycle from
                  this point.
                - `completed`: terminal success. Webhook delivery
                  attempted and acknowledged by every active endpoint, OR
                  no endpoints are configured, so the row is durably
                  archived.
                - `rejected`: terminal failure at ingestion (spam, blocked
                  sender, filter rule, malformed). The body and metadata
                  are stored for auditing but no webhook fires and the
                  row is not repliable.

              See also `webhook_status` (separate enum tracking the
              webhook-delivery state machine) and `SentEmailStatus` (the
              outbound vocabulary).
            enum:
              - pending
              - accepted
              - completed
              - rejected
          description: |
            Filter inbound rows by lifecycle status. See `EmailStatus`
            for what each value means. Note that the webhook delivery
            state is a SEPARATE lifecycle on the same row; filter by
            `webhook_status` semantics is not currently supported on
            this endpoint.
        - name: search
          in: query
          schema:
            type: string
            maxLength: 500
          description: Search subject, sender, and recipient (case-insensitive)
        - name: date_from
          in: query
          schema:
            type: string
            format: date-time
          description: Filter emails created on or after this timestamp
        - name: date_to
          in: query
          schema:
            type: string
            format: date-time
          description: Filter emails created on or before this timestamp
        - name: since
          in: query
          schema:
            type: string
            maxLength: 200
          description: |
            Forward-tail cursor. Returns rows that became visible AFTER this
            cursor, oldest-first, so a caller can stream new inbound mail by
            re-passing the cursor from each response. Mutually exclusive with
            `cursor` (which pages history newest-first). Pass the `meta.cursor`
            from the previous `since` response; an empty page means caught up.
        - name: wait
          in: query
          schema:
            type: integer
            minimum: 0
            maximum: 30
          description: |
            Long-poll: hold the request up to this many seconds waiting for new
            mail past `since`, returning as soon as any arrives (or an empty
            page when the wait elapses). Requires `since`. Omitted means no wait
            (returns immediately); the server treats an absent value as 0. NOT
            given an OpenAPI `default` on purpose: a default makes some
            generators (e.g. openapi-python-client) send `wait=0` on every call,
            which then fails the `wait` requires `since` check for plain history
            listings.
      responses:
        "200":
          description: Paginated list of emails
          content:
            application/json:
              schema:
                allOf:
                  - type: object
                    properties:
                      success:
                        type: boolean
                        const: true
                      meta:
                        type: object
                        properties:
                          total:
                            type: integer
                            description: Total number of matching records
                          limit:
                            type: integer
                            description: Page size used for this request
                          cursor:
                            type:
                              - string
                              - "null"
                            description: Cursor for the next page, or null if no more results
                        required:
                          - total
                          - limit
                          - cursor
                    required:
                      - success
                      - data
                      - meta
                  - type: object
                    properties:
                      data:
                        type: array
                        items:
                          type: object
                          properties:
                            id:
                              type: string
                              format: uuid
                            message_id:
                              type:
                                - string
                                - "null"
                            domain_id:
                              type:
                                - string
                                - "null"
                              format: uuid
                            org_id:
                              type:
                                - string
                                - "null"
                              format: uuid
                            status:
                              type: string
                              description: |
                                Lifecycle status of an INBOUND email (a row in the `emails`
                                table). Distinct from `SentEmailStatus`, which describes
                                the OUTBOUND lifecycle (the `sent_emails` table) and uses
                                a different vocabulary because the lifecycles differ.
                                Possible values:

                                  - `pending`: the row was inserted at ingestion (mx_main)
                                    and has not yet completed the spam / filter / auth
                                    pipeline. Body and parsed fields are present; webhook
                                    delivery is not yet scheduled. Most rows transition out
                                    of `pending` within seconds.
                                  - `accepted`: the inbound passed the policy gates and is
                                    queued for webhook delivery. The `webhook_status` field
                                    tracks the separate webhook-delivery lifecycle from
                                    this point.
                                  - `completed`: terminal success. Webhook delivery
                                    attempted and acknowledged by every active endpoint, OR
                                    no endpoints are configured, so the row is durably
                                    archived.
                                  - `rejected`: terminal failure at ingestion (spam, blocked
                                    sender, filter rule, malformed). The body and metadata
                                    are stored for auditing but no webhook fires and the
                                    row is not repliable.

                                See also `webhook_status` (separate enum tracking the
                                webhook-delivery state machine) and `SentEmailStatus` (the
                                outbound vocabulary).
                              enum:
                                - pending
                                - accepted
                                - completed
                                - rejected
                            sender:
                              type: string
                              description: |
                                SMTP envelope sender (return-path) the inbound mail server
                                accepted. For most legitimate mail this equals the bare
                                address in the From header; for mailing lists, bounce
                                handlers, and forwarders it is typically the bounce address
                                rather than the human-visible sender.

                                For the parsed From-header value (with display name handling
                                and a sender-fallback when the header is unparseable), GET
                                the email by id and use `from_email`.
                            recipient:
                              type: string
                            subject:
                              type:
                                - string
                                - "null"
                            domain:
                              type: string
                            spam_score:
                              type:
                                - number
                                - "null"
                            created_at:
                              type: string
                              format: date-time
                            received_at:
                              type: string
                              format: date-time
                            raw_size_bytes:
                              type:
                                - integer
                                - "null"
                            webhook_status:
                              type:
                                - string
                                - "null"
                              description: |
                                Webhook-delivery state for an inbound email. Tracks a
                                SEPARATE lifecycle from the email's `status` field; the
                                same row carries both. Possible values:

                                  - `pending`: ingestion is past `pending` (the email itself
                                    is `accepted`) but the webhook fan-out has not yet
                                    started for this row.
                                  - `in_flight`: at least one delivery attempt is in flight.
                                  - `fired`: terminal success. Every active endpoint
                                    acknowledged the delivery (or accepted it after retries).
                                  - `failed`: terminal partial-failure. At least one endpoint
                                    exhausted its retry budget; some endpoints may still
                                    have succeeded.
                                  - `exhausted`: terminal failure. Every endpoint exhausted
                                    its retry budget without success.
                                  - `null`: no endpoints configured, so no webhook lifecycle
                                    applies.

                                Note that the value `pending` here does NOT mean the email
                                is `pending`; it means the email is past ingestion but
                                webhook delivery has not yet begun. Two overlapping uses
                                of the word `pending` for distinct lifecycle phases.
                              enum:
                                - pending
                                - in_flight
                                - fired
                                - failed
                                - exhausted
                                - null
                            webhook_attempt_count:
                              type: integer
                            thread_id:
                              type:
                                - string
                                - "null"
                              format: uuid
                              description: |
                                Conversation thread this message belongs to. Fetch
                                `/threads/{thread_id}` for the full ordered thread. NULL on
                                messages received before threading was enabled.
                          required:
                            - id
                            - status
                            - sender
                            - recipient
                            - domain
                            - created_at
                            - received_at
                            - webhook_attempt_count
          headers:
            ratelimit-limit:
              description: Maximum number of requests allowed in the current window.
              schema:
                type: integer
                minimum: 1
                example: 120
            ratelimit-remaining:
              description: Remaining requests in the current window.
              schema:
                type: integer
                minimum: 0
                example: 118
            ratelimit-reset:
              description: Unix timestamp (seconds) when the current window resets.
              schema:
                type: integer
                example: 1700000060
            ratelimit-policy:
              description: Rate-limit policy in `limit;w=seconds` format, e.g. `120;w=60`.
              schema:
                type: string
                example: 120;w=60
        "400":
          $ref: "#/components/responses/ValidationError"
          description: Invalid request parameters
        "401":
          $ref: "#/components/responses/Unauthorized"
          description: Invalid or missing API key
      security:
        - BearerAuth: []
  /emails/search:
    get:
      operationId: searchEmails
      summary: Search inbound emails
      description: |
        Searches inbound emails with structured filters and optional
        full-text matching across parsed email fields. This endpoint is
        optimized for filtered inbox views and CLI polling workflows:
        callers that only need new accepted mail can pass
        `sort=received_at_asc`, `snippet=false`, `include_facets=false`,
        and a `date_from` timestamp.

        `q`, `subject`, and `body` use the same English full-text index
        as the web inbox search. Structured filters such as `from`, `to`,
        `domain_id`, status, attachment presence, and spam score bounds
        are combined with the text query.
      tags:
        - Emails
      parameters:
        - name: q
          in: query
          schema:
            type: string
            maxLength: 500
          description: Full-text search DSL query.
        - name: from
          in: query
          schema:
            type: string
            maxLength: 255
          description: Filter by sender address or sender domain.
        - name: to
          in: query
          schema:
            type: string
            maxLength: 255
          description: Filter by recipient address or recipient domain.
        - name: subject
          in: query
          schema:
            type: string
            maxLength: 500
          description: Full-text search restricted to the subject field.
        - name: body
          in: query
          schema:
            type: string
            maxLength: 2000
          description: Full-text search restricted to the parsed text body.
        - name: domain_id
          in: query
          schema:
            type: string
            format: uuid
          description: Filter by domain ID.
        - name: reply_to_sent_email_id
          in: query
          schema:
            type: string
            format: uuid
          description: |
            Filter to inbound emails that are replies to a specific
            outbound send. The value is a `sent_emails.id` (UUID). At
            inbound ingest, Primitive matches the parsed In-Reply-To
            header (or References as a fallback) against
            `sent_emails.message_id` in the same org and records the
            resolved id on `emails.reply_to_sent_email_id`. This filter
            is the strict-threading lookup behind `primitive chat` and
            any UI that wants to show the inbound reply to a given
            send. NULL on inbound that isn't a threaded reply to one
            of your sends, so existing emails received before this
            ingestion landed will not match.
        - name: status
          in: query
          schema:
            type: string
            description: |
              Lifecycle status of an INBOUND email (a row in the `emails`
              table). Distinct from `SentEmailStatus`, which describes
              the OUTBOUND lifecycle (the `sent_emails` table) and uses
              a different vocabulary because the lifecycles differ.
              Possible values:

                - `pending`: the row was inserted at ingestion (mx_main)
                  and has not yet completed the spam / filter / auth
                  pipeline. Body and parsed fields are present; webhook
                  delivery is not yet scheduled. Most rows transition out
                  of `pending` within seconds.
                - `accepted`: the inbound passed the policy gates and is
                  queued for webhook delivery. The `webhook_status` field
                  tracks the separate webhook-delivery lifecycle from
                  this point.
                - `completed`: terminal success. Webhook delivery
                  attempted and acknowledged by every active endpoint, OR
                  no endpoints are configured, so the row is durably
                  archived.
                - `rejected`: terminal failure at ingestion (spam, blocked
                  sender, filter rule, malformed). The body and metadata
                  are stored for auditing but no webhook fires and the
                  row is not repliable.

              See also `webhook_status` (separate enum tracking the
              webhook-delivery state machine) and `SentEmailStatus` (the
              outbound vocabulary).
            enum:
              - pending
              - accepted
              - completed
              - rejected
          description: Filter by inbound email lifecycle status.
        - name: date_from
          in: query
          schema:
            type: string
            format: date-time
          description: Filter emails received on or after this timestamp.
        - name: date_to
          in: query
          schema:
            type: string
            format: date-time
          description: Filter emails received on or before this timestamp.
        - name: has_attachment
          in: query
          schema:
            type: string
            enum:
              - "true"
              - "false"
          description: Filter by whether the email has one or more attachments.
        - name: spam_score_lt
          in: query
          schema:
            type: number
          description: Filter to emails with spam score below this value.
        - name: spam_score_gte
          in: query
          schema:
            type: number
          description: Filter to emails with spam score greater than or equal to this value.
        - name: sort
          in: query
          schema:
            type: string
            enum:
              - relevance
              - received_at_desc
              - received_at_asc
          description: |
            Sort mode. Defaults to relevance when a text query is present,
            otherwise `received_at_desc`.
        - name: cursor
          in: query
          schema:
            type: string
            maxLength: 200
          description: Opaque pagination cursor from a previous search response.
        - name: limit
          in: query
          schema:
            type: integer
            minimum: 1
            maximum: 100
            default: 50
          description: Number of results per page
        - name: snippet
          in: query
          schema:
            type: string
            enum:
              - "true"
              - "false"
            default: "true"
          description: Include subject/body highlight snippets when text search is active.
        - name: include_facets
          in: query
          schema:
            type: string
            enum:
              - "true"
              - "false"
            default: "true"
          description: Include facet counts for sender, domain, status, and attachment presence.
      responses:
        "200":
          description: Search results
          content:
            application/json:
              schema:
                allOf:
                  - type: object
                    properties:
                      success:
                        type: boolean
                        const: true
                    required:
                      - success
                      - data
                  - type: object
                    properties:
                      data:
                        type: array
                        items:
                          allOf:
                            - type: object
                              properties:
                                id:
                                  type: string
                                  format: uuid
                                message_id:
                                  type:
                                    - string
                                    - "null"
                                domain_id:
                                  type:
                                    - string
                                    - "null"
                                  format: uuid
                                org_id:
                                  type:
                                    - string
                                    - "null"
                                  format: uuid
                                status:
                                  type: string
                                  description: |
                                    Lifecycle status of an INBOUND email (a row in the `emails`
                                    table). Distinct from `SentEmailStatus`, which describes
                                    the OUTBOUND lifecycle (the `sent_emails` table) and uses
                                    a different vocabulary because the lifecycles differ.
                                    Possible values:

                                      - `pending`: the row was inserted at ingestion (mx_main)
                                        and has not yet completed the spam / filter / auth
                                        pipeline. Body and parsed fields are present; webhook
                                        delivery is not yet scheduled. Most rows transition out
                                        of `pending` within seconds.
                                      - `accepted`: the inbound passed the policy gates and is
                                        queued for webhook delivery. The `webhook_status` field
                                        tracks the separate webhook-delivery lifecycle from
                                        this point.
                                      - `completed`: terminal success. Webhook delivery
                                        attempted and acknowledged by every active endpoint, OR
                                        no endpoints are configured, so the row is durably
                                        archived.
                                      - `rejected`: terminal failure at ingestion (spam, blocked
                                        sender, filter rule, malformed). The body and metadata
                                        are stored for auditing but no webhook fires and the
                                        row is not repliable.

                                    See also `webhook_status` (separate enum tracking the
                                    webhook-delivery state machine) and `SentEmailStatus` (the
                                    outbound vocabulary).
                                  enum:
                                    - pending
                                    - accepted
                                    - completed
                                    - rejected
                                sender:
                                  type: string
                                  description: |
                                    SMTP envelope sender (return-path) the inbound mail server
                                    accepted. For most legitimate mail this equals the bare
                                    address in the From header; for mailing lists, bounce
                                    handlers, and forwarders it is typically the bounce address
                                    rather than the human-visible sender.

                                    For the parsed From-header value (with display name handling
                                    and a sender-fallback when the header is unparseable), GET
                                    the email by id and use `from_email`.
                                recipient:
                                  type: string
                                subject:
                                  type:
                                    - string
                                    - "null"
                                domain:
                                  type: string
                                spam_score:
                                  type:
                                    - number
                                    - "null"
                                created_at:
                                  type: string
                                  format: date-time
                                received_at:
                                  type: string
                                  format: date-time
                                raw_size_bytes:
                                  type:
                                    - integer
                                    - "null"
                                webhook_status:
                                  type:
                                    - string
                                    - "null"
                                  description: |
                                    Webhook-delivery state for an inbound email. Tracks a
                                    SEPARATE lifecycle from the email's `status` field; the
                                    same row carries both. Possible values:

                                      - `pending`: ingestion is past `pending` (the email itself
                                        is `accepted`) but the webhook fan-out has not yet
                                        started for this row.
                                      - `in_flight`: at least one delivery attempt is in flight.
                                      - `fired`: terminal success. Every active endpoint
                                        acknowledged the delivery (or accepted it after retries).
                                      - `failed`: terminal partial-failure. At least one endpoint
                                        exhausted its retry budget; some endpoints may still
                                        have succeeded.
                                      - `exhausted`: terminal failure. Every endpoint exhausted
                                        its retry budget without success.
                                      - `null`: no endpoints configured, so no webhook lifecycle
                                        applies.

                                    Note that the value `pending` here does NOT mean the email
                                    is `pending`; it means the email is past ingestion but
                                    webhook delivery has not yet begun. Two overlapping uses
                                    of the word `pending` for distinct lifecycle phases.
                                  enum:
                                    - pending
                                    - in_flight
                                    - fired
                                    - failed
                                    - exhausted
                                    - null
                                webhook_attempt_count:
                                  type: integer
                                thread_id:
                                  type:
                                    - string
                                    - "null"
                                  format: uuid
                                  description: |
                                    Conversation thread this message belongs to. Fetch
                                    `/threads/{thread_id}` for the full ordered thread. NULL on
                                    messages received before threading was enabled.
                              required:
                                - id
                                - status
                                - sender
                                - recipient
                                - domain
                                - created_at
                                - received_at
                                - webhook_attempt_count
                            - type: object
                              properties:
                                attachment_count:
                                  type: integer
                                  description: Number of parsed attachments on the email.
                                from_known_address:
                                  type: boolean
                                  description: Whether the parsed From address is known to this org from prior authenticated inbound mail.
                                score:
                                  type: number
                                  description: Relevance score. Present only when sorting by relevance.
                                highlights:
                                  type: object
                                  properties:
                                    subject:
                                      type: array
                                      items:
                                        type: string
                                      description: Subject snippets with matching terms highlighted.
                                    body:
                                      type: array
                                      items:
                                        type: string
                                      description: Body snippets with matching terms highlighted.
                                  required:
                                    - subject
                                    - body
                              required:
                                - attachment_count
                                - from_known_address
                      meta:
                        type: object
                        properties:
                          total:
                            type: integer
                            description: Total number of matching records, capped when `total_capped` is true.
                          total_capped:
                            type: boolean
                            description: Whether `total` was capped instead of counted exactly.
                          limit:
                            type: integer
                            description: Page size used for this request.
                          cursor:
                            type:
                              - string
                              - "null"
                            description: Cursor for the next search page, or null if no more results.
                          sort:
                            type: string
                            enum:
                              - relevance
                              - received_at_desc
                              - received_at_asc
                            description: Sort mode used for the result page.
                        required:
                          - total
                          - total_capped
                          - limit
                          - cursor
                          - sort
                      facets:
                        type: object
                        properties:
                          by_sender:
                            type: array
                            items:
                              type: object
                              properties:
                                value:
                                  type:
                                    - string
                                    - "null"
                                count:
                                  type: integer
                              required:
                                - value
                                - count
                          by_domain:
                            type: array
                            items:
                              type: object
                              properties:
                                value:
                                  type:
                                    - string
                                    - "null"
                                count:
                                  type: integer
                              required:
                                - value
                                - count
                          by_status:
                            type: array
                            items:
                              type: object
                              properties:
                                value:
                                  type:
                                    - string
                                    - "null"
                                count:
                                  type: integer
                              required:
                                - value
                                - count
                          has_attachment:
                            type: object
                            properties:
                              "true":
                                type: integer
                              "false":
                                type: integer
                            required:
                              - "true"
                              - "false"
                        required:
                          - by_sender
                          - by_domain
                          - by_status
                          - has_attachment
                    required:
                      - data
                      - meta
          headers:
            ratelimit-limit:
              description: Maximum number of requests allowed in the current window.
              schema:
                type: integer
                minimum: 1
                example: 120
            ratelimit-remaining:
              description: Remaining requests in the current window.
              schema:
                type: integer
                minimum: 0
                example: 118
            ratelimit-reset:
              description: Unix timestamp (seconds) when the current window resets.
              schema:
                type: integer
                example: 1700000060
            ratelimit-policy:
              description: Rate-limit policy in `limit;w=seconds` format, e.g. `120;w=60`.
              schema:
                type: string
                example: 120;w=60
        "400":
          $ref: "#/components/responses/ValidationError"
          description: Invalid request parameters
        "401":
          $ref: "#/components/responses/Unauthorized"
          description: Invalid or missing API key
        "504":
          $ref: "#/components/responses/GatewayTimeout"
          description: Search query timed out
      security:
        - BearerAuth: []
  /emails/{id}:
    parameters:
      - name: id
        in: path
        required: true
        schema:
          type: string
          format: uuid
        description: Resource UUID
    get:
      operationId: getEmail
      summary: Get inbound email by id
      description: |
        Returns the full record for an inbound email received at one
        of your verified domains, including the parsed text and HTML
        bodies, threading metadata, SMTP envelope detail, webhook
        delivery state, and a `replies` array for any outbound sends
        recorded as replies to this inbound.

        For listing inbound emails (with cursor pagination, status
        and date filters, and free-text search), use
        `/emails`. Outbound (sent) email records are NOT returned
        here; use `/sent-emails/{id}` for those.

        The response carries four sender-shaped fields whose
        meanings overlap. `from_email` is the canonical "who sent
        this" field for most use cases (parsed bare address from
        the `From:` header, with a `sender` fallback). `from_header`
        is the raw header including any display name. `sender` and
        `smtp_mail_from` both carry the SMTP envelope MAIL FROM
        (return-path) and are equal by construction; `sender` is
        the older field name retained for compatibility. See
        `primitive describe emails:get-email | jq '.responseSchema.properties'`
        for per-field detail.
      tags:
        - Emails
      responses:
        "200":
          description: Email details
          content:
            application/json:
              schema:
                allOf:
                  - type: object
                    properties:
                      success:
                        type: boolean
                        const: true
                    required:
                      - success
                      - data
                  - type: object
                    properties:
                      data:
                        type: object
                        properties:
                          id:
                            type: string
                            format: uuid
                          message_id:
                            type:
                              - string
                              - "null"
                          domain_id:
                            type:
                              - string
                              - "null"
                            format: uuid
                          org_id:
                            type:
                              - string
                              - "null"
                            format: uuid
                          sender:
                            type: string
                            description: |
                              SMTP envelope sender (return-path) the inbound mail server
                              accepted. Same value as `smtp_mail_from`; both fields exist
                              so protocol-aware tooling can use whichever name it expects.

                              For most legitimate mail this equals `from_email`; for
                              mailing lists, bounce handlers, and forwarders it is
                              typically the bounce-handling address rather than the
                              human-visible sender.

                              **For the canonical "who sent this email" value, use
                              `from_email`.**
                          recipient:
                            type: string
                          subject:
                            type:
                              - string
                              - "null"
                          body_text:
                            type:
                              - string
                              - "null"
                            description: Plain-text body parsed from the inbound MIME, matching the `email.parsed.body_text` field on the webhook payload. Null when the message had no text part or parsing failed.
                          body_html:
                            type:
                              - string
                              - "null"
                            description: HTML body parsed from the inbound MIME, matching the `email.parsed.body_html` field on the webhook payload. Null when the message had no HTML part or parsing failed.
                          status:
                            type: string
                            description: |
                              Lifecycle status of an INBOUND email (a row in the `emails`
                              table). Distinct from `SentEmailStatus`, which describes
                              the OUTBOUND lifecycle (the `sent_emails` table) and uses
                              a different vocabulary because the lifecycles differ.
                              Possible values:

                                - `pending`: the row was inserted at ingestion (mx_main)
                                  and has not yet completed the spam / filter / auth
                                  pipeline. Body and parsed fields are present; webhook
                                  delivery is not yet scheduled. Most rows transition out
                                  of `pending` within seconds.
                                - `accepted`: the inbound passed the policy gates and is
                                  queued for webhook delivery. The `webhook_status` field
                                  tracks the separate webhook-delivery lifecycle from
                                  this point.
                                - `completed`: terminal success. Webhook delivery
                                  attempted and acknowledged by every active endpoint, OR
                                  no endpoints are configured, so the row is durably
                                  archived.
                                - `rejected`: terminal failure at ingestion (spam, blocked
                                  sender, filter rule, malformed). The body and metadata
                                  are stored for auditing but no webhook fires and the
                                  row is not repliable.

                              See also `webhook_status` (separate enum tracking the
                              webhook-delivery state machine) and `SentEmailStatus` (the
                              outbound vocabulary).
                            enum:
                              - pending
                              - accepted
                              - completed
                              - rejected
                          domain:
                            type: string
                          spam_score:
                            type:
                              - number
                              - "null"
                          raw_size_bytes:
                            type:
                              - integer
                              - "null"
                          raw_sha256:
                            type:
                              - string
                              - "null"
                          created_at:
                            type: string
                            format: date-time
                          received_at:
                            type: string
                            format: date-time
                          rejection_reason:
                            type:
                              - string
                              - "null"
                          webhook_status:
                            type:
                              - string
                              - "null"
                            description: |
                              Webhook-delivery state for an inbound email. Tracks a
                              SEPARATE lifecycle from the email's `status` field; the
                              same row carries both. Possible values:

                                - `pending`: ingestion is past `pending` (the email itself
                                  is `accepted`) but the webhook fan-out has not yet
                                  started for this row.
                                - `in_flight`: at least one delivery attempt is in flight.
                                - `fired`: terminal success. Every active endpoint
                                  acknowledged the delivery (or accepted it after retries).
                                - `failed`: terminal partial-failure. At least one endpoint
                                  exhausted its retry budget; some endpoints may still
                                  have succeeded.
                                - `exhausted`: terminal failure. Every endpoint exhausted
                                  its retry budget without success.
                                - `null`: no endpoints configured, so no webhook lifecycle
                                  applies.

                              Note that the value `pending` here does NOT mean the email
                              is `pending`; it means the email is past ingestion but
                              webhook delivery has not yet begun. Two overlapping uses
                              of the word `pending` for distinct lifecycle phases.
                            enum:
                              - pending
                              - in_flight
                              - fired
                              - failed
                              - exhausted
                              - null
                          webhook_attempt_count:
                            type: integer
                          webhook_last_attempt_at:
                            type:
                              - string
                              - "null"
                            format: date-time
                          webhook_last_status_code:
                            type:
                              - integer
                              - "null"
                          webhook_last_error:
                            type:
                              - string
                              - "null"
                          webhook_fired_at:
                            type:
                              - string
                              - "null"
                            format: date-time
                          smtp_helo:
                            type:
                              - string
                              - "null"
                          smtp_mail_from:
                            type:
                              - string
                              - "null"
                            description: |
                              SMTP envelope MAIL FROM (return-path), as accepted by the
                              inbound mail server. Same value as `sender`; both fields
                              exist so protocol-aware tooling can use whichever name it
                              expects.

                              For the canonical "who sent this email" value (display name
                              stripped, From-header preferred), use `from_email`.
                          smtp_rcpt_to:
                            type:
                              - array
                              - "null"
                            items:
                              type: string
                          from_header:
                            type:
                              - string
                              - "null"
                            description: |
                              Raw `From:` header from the message body, including any
                              display name (e.g. `"Alice Example" <alice@example.com>`).
                              Use this when you need the display name for rendering.

                              For the bare email address (display name stripped), use
                              `from_email`.
                          content_discarded_at:
                            type:
                              - string
                              - "null"
                            format: date-time
                          content_discarded_by_delivery_id:
                            type:
                              - string
                              - "null"
                          from_email:
                            type: string
                            description: |
                              Bare email address parsed from the `From:` header, with
                              display name stripped (e.g. `alice@example.com`). Falls
                              back to `sender` (the SMTP envelope MAIL FROM) when the
                              `From:` header cannot be parsed.

                              **This is the canonical "who sent this email" field for
                              most use cases**, including comparing against allowlists,
                              routing replies, or displaying the sender to a user. Use
                              `from_header` when you specifically need the display name,
                              or `sender`/`smtp_mail_from` when you need the SMTP
                              envelope value (e.g. to follow a bounce).
                          to_email:
                            type: string
                            description: Parsed to address (same as recipient)
                          from_known_address:
                            type: boolean
                            description: |
                              True when the inbound's sender address has a matching grant
                              in the org's known-send-addresses list. Advisory: a true
                              value does not by itself guarantee that a reply will be
                              accepted by send-mail's gates; the per-send check at send
                              time remains authoritative.
                          replies:
                            type: array
                            description: |
                              Sent emails recorded as replies to this inbound, in send
                              order (ascending). Populated when a customer's send-mail
                              request carries an `in_reply_to` Message-ID that matches
                              this inbound's `message_id` in the same org. Includes
                              attempts that were gate-denied, so the array reflects every
                              recorded reply attempt regardless of outcome.
                            items:
                              type: object
                              properties:
                                id:
                                  type: string
                                  format: uuid
                                  description: Sent-email row id.
                                status:
                                  type: string
                                  description: |
                                    Lifecycle status of a sent_emails row. Possible values:

                                      - `queued`: pre-call INSERT; the outbound agent has not
                                        yet replied.
                                      - `submitted_to_agent`: agent accepted; `queue_id` is set.
                                      - `agent_failed`: agent rejected; `error_code` and
                                        `error_message` carry the reason.
                                      - `gate_denied`: a recipient-scope gate denied the send;
                                        the agent was never called. The `gates` array carries
                                        the denial detail. /send-mail returns 403 in this case
                                        so callers see the denial synchronously; /sent-emails
                                        additionally records the row for historical lookup,
                                        which is when this status appears in a listing.
                                      - `unknown`: terminal indeterminate; the on-box log
                                        poller couldn't classify the receiver's response.
                                      - `delivered` / `bounced` / `deferred` / `wait_timeout`:
                                        terminal delivery outcomes (see DeliveryStatus).
                                  enum:
                                    - queued
                                    - submitted_to_agent
                                    - agent_failed
                                    - gate_denied
                                    - unknown
                                    - delivered
                                    - bounced
                                    - deferred
                                    - wait_timeout
                                to_address:
                                  type: string
                                  description: Recipient address as recorded on the sent_emails row.
                                subject:
                                  type:
                                    - string
                                    - "null"
                                created_at:
                                  type: string
                                  format: date-time
                                queue_id:
                                  type:
                                    - string
                                    - "null"
                                  description: Outbound relay queue identifier when available.
                              required:
                                - id
                                - status
                                - to_address
                                - created_at
                          reply_to_sent_email_id:
                            type:
                              - string
                              - "null"
                            format: uuid
                            description: |
                              The `sent_emails.id` of the outbound this inbound was a
                              reply to, when resolvable. Set at inbound ingest by
                              matching the parsed In-Reply-To (or References, as a
                              fallback) against `sent_emails.message_id` in the same
                              org. The mirror of `sent_emails.in_reply_to_email_id` for
                              the inbound side of a thread. NULL when the inbound is
                              not a threaded reply to one of your sends, when neither
                              header survived the path through intermediate MTAs, or on
                              inbound received before this auto-link landed.
                          thread_id:
                            type:
                              - string
                              - "null"
                            format: uuid
                            description: |
                              Conversation thread this message belongs to. Inbound and
                              outbound messages in the same conversation share a
                              `thread_id`; fetch `/threads/{thread_id}` for the full
                              ordered thread. Assigned at ingest. NULL on messages
                              received before threading was enabled (until backfilled).
                          parsed:
                            allOf:
                              - type: object
                                description: |
                                  Parsed MIME content for an inbound email. Mirrors the
                                  `email.parsed` object on the webhook payload so a single parser
                                  handles both surfaces. `status` is `complete` when parsing
                                  succeeded; on `failed` the body/address/attachment fields are
                                  absent and `error` describes why.
                                properties:
                                  status:
                                    type: string
                                    enum:
                                      - complete
                                      - failed
                                  body_text:
                                    type:
                                      - string
                                      - "null"
                                    description: Plain-text body. Present when `status` is `complete`.
                                  body_html:
                                    type:
                                      - string
                                      - "null"
                                    description: HTML body. Present when `status` is `complete`.
                                  reply_to:
                                    type:
                                      - array
                                      - "null"
                                    items:
                                      type: object
                                      description: A parsed RFC 5322 address with optional display name.
                                      properties:
                                        name:
                                          type:
                                            - string
                                            - "null"
                                          description: Display name, when present (e.g. `Alice Example`).
                                        address:
                                          type: string
                                          description: Bare email address (e.g. `alice@example.com`).
                                      required:
                                        - address
                                    description: Parsed `Reply-To` header addresses.
                                  cc:
                                    type:
                                      - array
                                      - "null"
                                    items:
                                      type: object
                                      description: A parsed RFC 5322 address with optional display name.
                                      properties:
                                        name:
                                          type:
                                            - string
                                            - "null"
                                          description: Display name, when present (e.g. `Alice Example`).
                                        address:
                                          type: string
                                          description: Bare email address (e.g. `alice@example.com`).
                                      required:
                                        - address
                                    description: Parsed `Cc` header addresses.
                                  bcc:
                                    type:
                                      - array
                                      - "null"
                                    items:
                                      type: object
                                      description: A parsed RFC 5322 address with optional display name.
                                      properties:
                                        name:
                                          type:
                                            - string
                                            - "null"
                                          description: Display name, when present (e.g. `Alice Example`).
                                        address:
                                          type: string
                                          description: Bare email address (e.g. `alice@example.com`).
                                      required:
                                        - address
                                    description: Parsed `Bcc` header addresses (rarely present on inbound).
                                  to_addresses:
                                    type:
                                      - array
                                      - "null"
                                    items:
                                      type: object
                                      description: A parsed RFC 5322 address with optional display name.
                                      properties:
                                        name:
                                          type:
                                            - string
                                            - "null"
                                          description: Display name, when present (e.g. `Alice Example`).
                                        address:
                                          type: string
                                          description: Bare email address (e.g. `alice@example.com`).
                                      required:
                                        - address
                                    description: Parsed `To` header addresses.
                                  in_reply_to:
                                    type:
                                      - array
                                      - "null"
                                    items:
                                      type: string
                                    description: Message-IDs from the `In-Reply-To` header.
                                  references:
                                    type:
                                      - array
                                      - "null"
                                    items:
                                      type: string
                                    description: Message-IDs from the `References` header.
                                  attachments:
                                    type: array
                                    items:
                                      type: object
                                      description: |
                                        Metadata for one attachment. The bytes are not inline; download
                                        all attachments for a message as a gzipped tarball via
                                        `/emails/{id}/attachments.tar.gz`. `sha256` lets you verify a
                                        specific part after extraction.
                                      properties:
                                        filename:
                                          type:
                                            - string
                                            - "null"
                                        content_type:
                                          type:
                                            - string
                                            - "null"
                                        size_bytes:
                                          type: integer
                                        sha256:
                                          type:
                                            - string
                                            - "null"
                                        part_index:
                                          type: integer
                                          description: Zero-based index of this part within the message.
                                      required:
                                        - size_bytes
                                    description: Attachment metadata. Empty array when none.
                                  error:
                                    type:
                                      - object
                                      - "null"
                                    description: |
                                      Present (non-null) only when `status` is `failed`. When
                                      present, all three fields are populated, so a consumer can
                                      branch on `code` without defensive null checks.
                                    properties:
                                      code:
                                        type: string
                                        description: Stable failure code (e.g. `PARSE_FAILED`).
                                      message:
                                        type: string
                                      retryable:
                                        type: boolean
                                    required:
                                      - code
                                      - message
                                      - retryable
                                required:
                                  - status
                            description: |
                              Parsed MIME content (addresses, threading headers,
                              attachment metadata), matching the `email.parsed` object
                              on the webhook payload so one parser handles both the
                              webhook and this endpoint. The top-level `body_text` /
                              `body_html` fields above are the same values as
                              `parsed.body_text` / `parsed.body_html`, retained for
                              backward compatibility.
                          auth:
                            allOf:
                              - type: object
                                description: |
                                  SPF / DKIM / DMARC verdicts computed at ingest. Mirrors the
                                  `email.auth` object on the webhook payload. Field names are
                                  camelCase to match that payload exactly. For messages received
                                  before auth was recorded, the verdicts default to `none`.
                                properties:
                                  spf:
                                    type: string
                                    description: SPF result (e.g. `pass`, `fail`, `softfail`, `none`).
                                  dmarc:
                                    type: string
                                    description: DMARC result (e.g. `pass`, `fail`, `none`).
                                  dmarcPolicy:
                                    type:
                                      - string
                                      - "null"
                                    description: Published DMARC policy (`none`, `quarantine`, `reject`).
                                  dmarcFromDomain:
                                    type:
                                      - string
                                      - "null"
                                    description: The From-header domain DMARC was evaluated against.
                                  dmarcSpfAligned:
                                    type: boolean
                                  dmarcDkimAligned:
                                    type: boolean
                                  dmarcSpfStrict:
                                    type:
                                      - boolean
                                      - "null"
                                  dmarcDkimStrict:
                                    type:
                                      - boolean
                                      - "null"
                                  dkimSignatures:
                                    type: array
                                    items:
                                      type: object
                                      description: One DKIM signature found on the message, with its verdict.
                                      properties:
                                        domain:
                                          type: string
                                        selector:
                                          type: string
                                        result:
                                          type: string
                                          description: Verification result (e.g. `pass`, `fail`, `none`).
                                        aligned:
                                          type: boolean
                                          description: Whether the signing domain aligns with the From domain (for DMARC).
                                        keyBits:
                                          type:
                                            - integer
                                            - "null"
                                        algo:
                                          type:
                                            - string
                                            - "null"
                                      required:
                                        - domain
                                        - selector
                                        - result
                                        - aligned
                                required:
                                  - spf
                                  - dmarc
                                  - dmarcSpfAligned
                                  - dmarcDkimAligned
                                  - dkimSignatures
                            description: |
                              SPF / DKIM / DMARC verdicts computed at ingest, matching
                              the `email.auth` object on the webhook payload. Use these
                              to decide how much to trust a message before acting on
                              instructions it contains.
                        required:
                          - id
                          - sender
                          - recipient
                          - status
                          - domain
                          - created_at
                          - received_at
                          - webhook_attempt_count
                          - from_email
                          - to_email
                          - replies
                          - parsed
                          - auth
          headers:
            ratelimit-limit:
              description: Maximum number of requests allowed in the current window.
              schema:
                type: integer
                minimum: 1
                example: 120
            ratelimit-remaining:
              description: Remaining requests in the current window.
              schema:
                type: integer
                minimum: 0
                example: 118
            ratelimit-reset:
              description: Unix timestamp (seconds) when the current window resets.
              schema:
                type: integer
                example: 1700000060
            ratelimit-policy:
              description: Rate-limit policy in `limit;w=seconds` format, e.g. `120;w=60`.
              schema:
                type: string
                example: 120;w=60
        "400":
          $ref: "#/components/responses/ValidationError"
          description: Invalid request parameters
        "401":
          $ref: "#/components/responses/Unauthorized"
          description: Invalid or missing API key
        "404":
          $ref: "#/components/responses/NotFound"
          description: Resource not found
      security:
        - BearerAuth: []
    delete:
      operationId: deleteEmail
      summary: Delete an email
      tags:
        - Emails
      responses:
        "200":
          description: Resource deleted
          content:
            application/json:
              schema:
                allOf:
                  - type: object
                    properties:
                      success:
                        type: boolean
                        const: true
                    required:
                      - success
                      - data
                  - type: object
                    properties:
                      data:
                        type: object
                        properties:
                          deleted:
                            type: boolean
                            const: true
                        required:
                          - deleted
          headers:
            ratelimit-limit:
              description: Maximum number of requests allowed in the current window.
              schema:
                type: integer
                minimum: 1
                example: 120
            ratelimit-remaining:
              description: Remaining requests in the current window.
              schema:
                type: integer
                minimum: 0
                example: 118
            ratelimit-reset:
              description: Unix timestamp (seconds) when the current window resets.
              schema:
                type: integer
                example: 1700000060
            ratelimit-policy:
              description: Rate-limit policy in `limit;w=seconds` format, e.g. `120;w=60`.
              schema:
                type: string
                example: 120;w=60
        "400":
          $ref: "#/components/responses/ValidationError"
          description: Invalid request parameters
        "401":
          $ref: "#/components/responses/Unauthorized"
          description: Invalid or missing API key
        "404":
          $ref: "#/components/responses/NotFound"
          description: Resource not found
      security:
        - BearerAuth: []
      description: Permanently delete a received email and its stored content. This cannot be undone.
  /emails/{id}/raw:
    parameters:
      - name: id
        in: path
        required: true
        schema:
          type: string
          format: uuid
        description: Resource UUID
    get:
      operationId: downloadRawEmail
      summary: Download raw email
      description: |
        Downloads the raw RFC 822 email file (.eml). Authenticates via
        a signed download token (provided in webhook payloads) or a
        valid session.
      tags:
        - Emails
      security:
        - BearerAuth: []
        - DownloadToken: []
      parameters:
        - name: token
          in: query
          schema:
            type: string
          description: Signed download token from webhook payload
      responses:
        "200":
          description: Raw email file
          content:
            message/rfc822:
              schema:
                type: string
                format: binary
          headers:
            ratelimit-limit:
              description: Maximum number of requests allowed in the current window.
              schema:
                type: integer
                minimum: 1
                example: 120
            ratelimit-remaining:
              description: Remaining requests in the current window.
              schema:
                type: integer
                minimum: 0
                example: 118
            ratelimit-reset:
              description: Unix timestamp (seconds) when the current window resets.
              schema:
                type: integer
                example: 1700000060
            ratelimit-policy:
              description: Rate-limit policy in `limit;w=seconds` format, e.g. `120;w=60`.
              schema:
                type: string
                example: 120;w=60
            Content-Disposition:
              schema:
                type: string
                example: attachment; filename="email-id.eml"
            X-Content-SHA256:
              schema:
                type: string
              description: SHA-256 hex digest of the file
        "400":
          $ref: "#/components/responses/ValidationError"
          description: Invalid request parameters
        "401":
          $ref: "#/components/responses/Unauthorized"
          description: Invalid or missing API key
        "404":
          $ref: "#/components/responses/NotFound"
          description: Resource not found
  /emails/{id}/attachments.tar.gz:
    parameters:
      - name: id
        in: path
        required: true
        schema:
          type: string
          format: uuid
        description: Resource UUID
    get:
      operationId: downloadAttachments
      summary: Download email attachments
      description: |
        Downloads all attachments as a gzip-compressed tar archive.
        Authenticates via a signed download token (provided in webhook
        payloads) or a valid session.
      tags:
        - Emails
      security:
        - BearerAuth: []
        - DownloadToken: []
      parameters:
        - name: token
          in: query
          schema:
            type: string
          description: Signed download token from webhook payload
      responses:
        "200":
          description: Attachments archive
          content:
            application/gzip:
              schema:
                type: string
                format: binary
          headers:
            ratelimit-limit:
              description: Maximum number of requests allowed in the current window.
              schema:
                type: integer
                minimum: 1
                example: 120
            ratelimit-remaining:
              description: Remaining requests in the current window.
              schema:
                type: integer
                minimum: 0
                example: 118
            ratelimit-reset:
              description: Unix timestamp (seconds) when the current window resets.
              schema:
                type: integer
                example: 1700000060
            ratelimit-policy:
              description: Rate-limit policy in `limit;w=seconds` format, e.g. `120;w=60`.
              schema:
                type: string
                example: 120;w=60
            Content-Disposition:
              schema:
                type: string
                example: attachment; filename="email-id_attachments.tar.gz"
            X-Content-SHA256:
              schema:
                type: string
              description: SHA-256 hex digest of the archive
            X-Attachment-Count:
              schema:
                type: string
              description: Number of attachments in the archive
        "400":
          $ref: "#/components/responses/ValidationError"
          description: Invalid request parameters
        "401":
          $ref: "#/components/responses/Unauthorized"
          description: Invalid or missing API key
        "404":
          $ref: "#/components/responses/NotFound"
          description: Resource not found
  /emails/{id}/reply:
    parameters:
      - name: id
        in: path
        required: true
        schema:
          type: string
          format: uuid
        description: Resource UUID
    post:
      operationId: replyToEmail
      summary: Reply to an inbound email
      description: |
        Sends an outbound reply to the inbound email identified by `id`.
        Threading headers (`In-Reply-To`, `References`), recipient
        derivation (Reply-To, then From, then bare sender), and the
        `Re:` subject prefix are all derived server-side from the
        stored inbound row. The request body carries only the message
        body, optional From override, optional attachments, and optional
        `wait` flag; passing any header or recipient override is
        rejected by the schema (`additionalProperties: false`).

        Forwards through the same gates as `/send-mail`: the response
        status, error envelope, and `idempotent_replay` flag mirror
        the send-mail contract verbatim.
      servers:
        - url: https://api.primitive.dev/v1
          description: Canonical API host (recommended)
        - url: https://www.primitive.dev/api/v1
          description: Legacy compatibility host (Vercel body limit applies)
      tags:
        - Sending
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              additionalProperties: false
              description: |
                Body shape for `/emails/{id}/reply`. Intentionally narrow:
                recipients (`to`), subject, and threading headers
                (`in_reply_to`, `references`) are derived server-side from
                the inbound row referenced by the path id and are rejected by
                `additionalProperties` if passed (returns 400).

                `from` IS allowed because of legitimate use cases (display-name
                addition, replying from a different verified outbound address,
                multi-team triage). Send-mail's per-send `canSendFrom` gate
                validates the from-domain regardless, so the override carries
                no extra privilege.
              properties:
                body_text:
                  type: string
                  description: Plain-text reply body. At least one of body_text or body_html is required. The combined UTF-8 byte length of body_text and body_html must be at most 262144 bytes (same cap as send-mail).
                body_html:
                  type: string
                  description: HTML reply body. At least one of body_text or body_html is required.
                from:
                  type: string
                  minLength: 3
                  maxLength: 998
                  description: |
                    Optional override for the reply's From header. Defaults to
                    the inbound's recipient. Use to add a display name (`"Acme
                    Support" <agent@company.com>`) or to reply from a different
                    verified outbound address (e.g. multi-team routing where
                    support@ triages to billing@). The from-domain must be a
                    verified outbound domain for your org, same as send-mail.
                wait:
                  type: boolean
                  description: When true, wait for the first downstream SMTP delivery outcome before returning, mirroring the send-mail `wait` semantics.
                attachments:
                  type: array
                  maxItems: 100
                  description: Inline attachments for this reply. Use https://api.primitive.dev/v1 for replies with attachments. Combined raw decoded attachment bytes must be at most 31457280.
                  items:
                    type: object
                    additionalProperties: false
                    properties:
                      filename:
                        type: string
                        minLength: 1
                        maxLength: 255
                        description: Attachment filename. Control characters are rejected.
                      content_type:
                        type: string
                        minLength: 1
                        maxLength: 255
                        description: Optional MIME content type. Control characters are rejected.
                      content_base64:
                        type: string
                        minLength: 1
                        maxLength: 44040192
                        description: Base64-encoded attachment bytes.
                    required:
                      - filename
                      - content_base64
      responses:
        "200":
          description: Outbound relay result
          content:
            application/json:
              schema:
                allOf:
                  - type: object
                    properties:
                      success:
                        type: boolean
                        const: true
                    required:
                      - success
                      - data
                  - type: object
                    properties:
                      data:
                        type: object
                        properties:
                          id:
                            type: string
                            description: Persisted sent-email attempt ID.
                          status:
                            type: string
                            description: |
                              Lifecycle status of a sent_emails row. Possible values:

                                - `queued`: pre-call INSERT; the outbound agent has not
                                  yet replied.
                                - `submitted_to_agent`: agent accepted; `queue_id` is set.
                                - `agent_failed`: agent rejected; `error_code` and
                                  `error_message` carry the reason.
                                - `gate_denied`: a recipient-scope gate denied the send;
                                  the agent was never called. The `gates` array carries
                                  the denial detail. /send-mail returns 403 in this case
                                  so callers see the denial synchronously; /sent-emails
                                  additionally records the row for historical lookup,
                                  which is when this status appears in a listing.
                                - `unknown`: terminal indeterminate; the on-box log
                                  poller couldn't classify the receiver's response.
                                - `delivered` / `bounced` / `deferred` / `wait_timeout`:
                                  terminal delivery outcomes (see DeliveryStatus).
                            enum:
                              - queued
                              - submitted_to_agent
                              - agent_failed
                              - gate_denied
                              - unknown
                              - delivered
                              - bounced
                              - deferred
                              - wait_timeout
                          from:
                            type: string
                            description: |
                              Bare from-address actually written on the wire. Echoed
                              on every success branch so callers can confirm what
                              went out, particularly useful for the /emails/{id}/reply
                              path where `from` is server-derived from the inbound's
                              recipient when the caller doesn't override.

                              For sends where the caller passed a from-header that
                              included a display name (e.g. `"Acme Support" <support@acme.test>`),
                              this field is the parsed bare address (`support@acme.test`).
                              The display name was sent on the wire intact; this field
                              just makes the address easy to compare against allowlists.
                          queue_id:
                            type:
                              - string
                              - "null"
                            description: |
                              Message identifier assigned by Primitive's OUTBOUND relay
                              (the box that signs your mail and submits it to the
                              receiving MTA). NOT the receiver's queue id.

                              The receiver may also report its own queue id in
                              `smtp_response_text` (e.g. `"250 2.0.0 Ok: queued as
                              99D111927CDA"` from a Postfix receiver). Those two ids
                              refer to different mail systems and are NOT comparable.
                              Treat `queue_id` as Primitive-internal and the
                              receiver's id as remote-system-internal.

                              Null on rows that never reached the relay (queued,
                              gate_denied, agent_failed before signing).
                          accepted:
                            type: array
                            items:
                              type: string
                            description: Recipient addresses accepted by the relay.
                          rejected:
                            type: array
                            items:
                              type: string
                            description: Recipient addresses rejected by the relay.
                          client_idempotency_key:
                            type: string
                            description: Effective idempotency key used for this send.
                          request_id:
                            type: string
                            description: Server-issued request identifier for support and tracing.
                          content_hash:
                            type: string
                            description: Stable hash of the canonical send payload.
                          delivery_status:
                            type: string
                            description: |
                              Narrower enum covering only the four terminal delivery
                              outcomes returned to a synchronous `wait: true` send.

                              On the SendMailResult shape, `delivery_status` is always
                              equal to `status` whenever both are present (i.e. on
                              terminal-state replays and live wait=true responses).
                              The two fields exist so callers that want to type-narrow
                              on "this is a delivery outcome" can pattern-match against
                              the four-value enum without handling the broader
                              SentEmailStatus value set (which also covers `queued`,
                              `submitted_to_agent`, `agent_failed`, `gate_denied`,
                              `unknown`).

                              On async-mode and pre-terminal responses, `delivery_status`
                              is absent and only `status` is populated. Use `status` if
                              you want a single field that's always present.
                            enum:
                              - delivered
                              - bounced
                              - deferred
                              - wait_timeout
                          smtp_response_code:
                            type:
                              - integer
                              - "null"
                            description: SMTP response code from the first downstream delivery outcome when wait is true.
                          smtp_response_text:
                            type: string
                            description: SMTP response text from the first downstream delivery outcome when wait is true.
                          idempotent_replay:
                            type: boolean
                            description: |
                              True when the response replays a previously-recorded send
                              keyed by `client_idempotency_key` (same key, same canonical
                              payload). False on a fresh send and on gate-denied
                              responses. Lets callers branch on cache state without
                              diffing fields.
                        required:
                          - id
                          - status
                          - from
                          - queue_id
                          - accepted
                          - rejected
                          - client_idempotency_key
                          - request_id
                          - content_hash
                          - idempotent_replay
          headers:
            ratelimit-limit:
              description: Maximum number of requests allowed in the current window.
              schema:
                type: integer
                minimum: 1
                example: 120
            ratelimit-remaining:
              description: Remaining requests in the current window.
              schema:
                type: integer
                minimum: 0
                example: 118
            ratelimit-reset:
              description: Unix timestamp (seconds) when the current window resets.
              schema:
                type: integer
                example: 1700000060
            ratelimit-policy:
              description: Rate-limit policy in `limit;w=seconds` format, e.g. `120;w=60`.
              schema:
                type: string
                example: 120;w=60
        "400":
          $ref: "#/components/responses/ValidationError"
          description: Invalid request parameters
        "401":
          $ref: "#/components/responses/Unauthorized"
          description: Invalid or missing API key
        "403":
          $ref: "#/components/responses/Forbidden"
          description: Authenticated caller lacks permission for the operation
        "404":
          $ref: "#/components/responses/NotFound"
          description: Resource not found
        "422":
          $ref: "#/components/responses/UnprocessableEntity"
          description: |
            Inbound is not repliable: the row exists but lacks a
            `message_id` (no thread anchor) or a `recipient` (cannot
            derive the From address).
        "429":
          $ref: "#/components/responses/RateLimited"
          description: Rate limit exceeded
        "500":
          $ref: "#/components/responses/InternalError"
          description: Primitive encountered an internal error
        "502":
          $ref: "#/components/responses/BadGateway"
          description: Primitive could not complete the downstream SMTP request
        "503":
          $ref: "#/components/responses/ServiceUnavailable"
          description: Primitive is temporarily unable to process the request
      security:
        - BearerAuth: []
      parameters:
        - name: Idempotency-Key
          in: header
          required: false
          description: Optional client-supplied idempotency key. Retrying a request with the same key returns the original result instead of performing the action a second time; if omitted the server derives one from the canonical payload hash. Safe to retry network failures without duplicating side effects.
          schema:
            type: string
            minLength: 1
            maxLength: 255
  /emails/{id}/replay:
    parameters:
      - name: id
        in: path
        required: true
        schema:
          type: string
          format: uuid
        description: Resource UUID
    post:
      operationId: replayEmailWebhooks
      summary: Replay email webhooks
      description: |
        Re-delivers the webhook payload for this email to all active
        endpoints matching the email's domain. Rate limited per-email
        (short cooldown between successive replays of the same email)
        and per-org (burst + sustained windows), sharing an org-wide
        budget with delivery replays.
      tags:
        - Emails
      responses:
        "200":
          description: Replay result
          content:
            application/json:
              schema:
                allOf:
                  - type: object
                    properties:
                      success:
                        type: boolean
                        const: true
                    required:
                      - success
                      - data
                  - type: object
                    properties:
                      data:
                        type: object
                        properties:
                          delivered:
                            type: integer
                            description: Number of successful deliveries
                          failed:
                            type: integer
                            description: Number of failed deliveries
                        required:
                          - delivered
                          - failed
          headers:
            ratelimit-limit:
              description: Maximum number of requests allowed in the current window.
              schema:
                type: integer
                minimum: 1
                example: 120
            ratelimit-remaining:
              description: Remaining requests in the current window.
              schema:
                type: integer
                minimum: 0
                example: 118
            ratelimit-reset:
              description: Unix timestamp (seconds) when the current window resets.
              schema:
                type: integer
                example: 1700000060
            ratelimit-policy:
              description: Rate-limit policy in `limit;w=seconds` format, e.g. `120;w=60`.
              schema:
                type: string
                example: 120;w=60
        "400":
          $ref: "#/components/responses/ValidationError"
          description: Invalid request parameters
        "401":
          $ref: "#/components/responses/Unauthorized"
          description: Invalid or missing API key
        "404":
          $ref: "#/components/responses/NotFound"
          description: Resource not found
        "429":
          $ref: "#/components/responses/RateLimited"
          description: Rate limit exceeded
      security:
        - BearerAuth: []
      parameters:
        - name: Idempotency-Key
          in: header
          required: false
          description: Optional client-supplied idempotency key. Retrying a request with the same key returns the original result instead of performing the action a second time; if omitted the server derives one from the canonical payload hash. Safe to retry network failures without duplicating side effects.
          schema:
            type: string
            minLength: 1
            maxLength: 255
  /emails/{id}/discard-content:
    parameters:
      - name: id
        in: path
        required: true
        schema:
          type: string
          format: uuid
        description: Resource UUID
    post:
      operationId: discardEmailContent
      summary: Discard email content
      description: |
        Permanently deletes the email's raw bytes, parsed body (text + HTML),
        and attachments while preserving metadata (sender, recipient,
        subject, timestamps, hashes, attachment manifest) for audit logs.
        Idempotent: a second call returns success with
        `already_discarded: true` and does no work.

        **Gated** on the customer's discard-content opt-in (managed in the
        dashboard at Settings > Webhooks). When the toggle is off, this
        endpoint returns `403` with code `discard_not_enabled` and a
        message pointing the human at the dashboard. There is intentionally
        no API to flip this toggle. Opting in to a destructive,
        non-reversible operation must be a deliberate human click in the
        UI.
      tags:
        - Emails
      responses:
        "200":
          description: Discard result
          content:
            application/json:
              schema:
                allOf:
                  - type: object
                    properties:
                      success:
                        type: boolean
                        const: true
                    required:
                      - success
                      - data
                  - type: object
                    properties:
                      data:
                        type: object
                        properties:
                          discarded:
                            type: boolean
                            description: |
                              Always `true` on a 2xx response. The content is either now
                              discarded as a result of this call, or was already discarded
                              before this call ran.
                          already_discarded:
                            type: boolean
                            description: |
                              `true` if the email's content was already discarded before
                              this call ran (no work was done). `false` if this call was
                              the one that performed the discard.
                        required:
                          - discarded
                          - already_discarded
                    required:
                      - data
          headers:
            ratelimit-limit:
              description: Maximum number of requests allowed in the current window.
              schema:
                type: integer
                minimum: 1
                example: 120
            ratelimit-remaining:
              description: Remaining requests in the current window.
              schema:
                type: integer
                minimum: 0
                example: 118
            ratelimit-reset:
              description: Unix timestamp (seconds) when the current window resets.
              schema:
                type: integer
                example: 1700000060
            ratelimit-policy:
              description: Rate-limit policy in `limit;w=seconds` format, e.g. `120;w=60`.
              schema:
                type: string
                example: 120;w=60
        "400":
          $ref: "#/components/responses/ValidationError"
          description: Invalid request parameters
        "401":
          $ref: "#/components/responses/Unauthorized"
          description: Invalid or missing API key
        "403":
          $ref: "#/components/responses/Forbidden"
          description: Authenticated caller lacks permission for the operation
        "404":
          $ref: "#/components/responses/NotFound"
          description: Resource not found
        "429":
          $ref: "#/components/responses/RateLimited"
          description: Rate limit exceeded
        "500":
          $ref: "#/components/responses/InternalError"
          description: Primitive encountered an internal error
      security:
        - BearerAuth: []
      parameters:
        - name: Idempotency-Key
          in: header
          required: false
          description: Optional client-supplied idempotency key. Retrying a request with the same key returns the original result instead of performing the action a second time; if omitted the server derives one from the canonical payload hash. Safe to retry network failures without duplicating side effects.
          schema:
            type: string
            minLength: 1
            maxLength: 255
  /emails/{id}/conversation:
    parameters:
      - name: id
        in: path
        required: true
        schema:
          type: string
          format: uuid
        description: Resource UUID
    get:
      operationId: getConversation
      summary: Get the conversation an email belongs to
      description: |
        Returns the full conversation the given inbound email belongs
        to, as ordered, ready-to-prompt turns WITH bodies. It resolves
        the thread from the email and returns every message oldest-first,
        so an agent that received an email can pass `messages` straight
        to a chat model in one call instead of walking `/threads/{id}`
        plus `/emails/{id}` and `/sent-emails/{id}` per message.

        Each message carries a `direction` (`inbound` | `outbound`) and a
        derived `role`: `inbound` -> `user`, `outbound` -> `assistant`
        (your own prior replies). The role mapping assumes the caller
        owns the outbound side, which is the agent-reply case this exists
        for. If the email has no thread yet (a brand-new message), the
        conversation is just that one message as a single user turn.

        The message list is capped; check `truncated` to detect when
        older messages were omitted. Consecutive same-role turns are not
        merged here; that normalization is model-specific and left to the
        caller.
      tags:
        - Emails
      responses:
        "200":
          description: Conversation
          content:
            application/json:
              schema:
                allOf:
                  - type: object
                    properties:
                      success:
                        type: boolean
                        const: true
                    required:
                      - success
                      - data
                  - type: object
                    properties:
                      data:
                        type: object
                        description: |
                          The full conversation an inbound email belongs to, as ordered,
                          ready-to-prompt turns with bodies. Resolves the thread from the
                          email and returns every message oldest-first, so an agent that
                          received an email can pass `messages` straight to a chat model in
                          one call.
                        properties:
                          thread_id:
                            type:
                              - string
                              - "null"
                            format: uuid
                            description: |
                              The thread this email belongs to, or null when the email
                              isn't threaded yet (the conversation is then just this one
                              message).
                          subject:
                            type:
                              - string
                              - "null"
                            description: |
                              Normalized thread subject (Re/Fwd prefixes stripped), or the
                              email's own subject when it isn't threaded.
                          message_count:
                            type: integer
                            description: |
                              Total messages in the thread. `messages` is capped, so
                              `truncated` is true (and this can exceed `messages.length`)
                              when older messages were omitted.
                          truncated:
                            type: boolean
                            description: |
                              True when `messages` omits part of the conversation because
                              the thread exceeds the per-call cap.
                          messages:
                            type: array
                            items:
                              type: object
                              description: One message in the conversation, with its body and a chat role.
                              properties:
                                role:
                                  type: string
                                  enum:
                                    - user
                                    - assistant
                                  description: |
                                    Chat role derived from `direction`: `user` for inbound
                                    (received) messages, `assistant` for outbound (your own prior
                                    replies). Lets `messages` be passed directly to a chat model.
                                direction:
                                  type: string
                                  enum:
                                    - inbound
                                    - outbound
                                  description: |
                                    `inbound` for a received email (`/emails/{id}`), `outbound`
                                    for a send (`/sent-emails/{id}`).
                                id:
                                  type: string
                                  format: uuid
                                message_id:
                                  type:
                                    - string
                                    - "null"
                                from:
                                  type:
                                    - string
                                    - "null"
                                to:
                                  type:
                                    - string
                                    - "null"
                                subject:
                                  type:
                                    - string
                                    - "null"
                                text:
                                  type: string
                                  description: |
                                    Plain-text body. Empty string when the message has no text
                                    part or its content was discarded by retention.
                                timestamp:
                                  type:
                                    - string
                                    - "null"
                                  format: date-time
                                  description: received_at for inbound, created_at for outbound.
                              required:
                                - role
                                - direction
                                - id
                                - text
                        required:
                          - thread_id
                          - message_count
                          - truncated
                          - messages
          headers:
            ratelimit-limit:
              description: Maximum number of requests allowed in the current window.
              schema:
                type: integer
                minimum: 1
                example: 120
            ratelimit-remaining:
              description: Remaining requests in the current window.
              schema:
                type: integer
                minimum: 0
                example: 118
            ratelimit-reset:
              description: Unix timestamp (seconds) when the current window resets.
              schema:
                type: integer
                example: 1700000060
            ratelimit-policy:
              description: Rate-limit policy in `limit;w=seconds` format, e.g. `120;w=60`.
              schema:
                type: string
                example: 120;w=60
        "400":
          $ref: "#/components/responses/ValidationError"
          description: Invalid request parameters
        "401":
          $ref: "#/components/responses/Unauthorized"
          description: Invalid or missing API key
        "404":
          $ref: "#/components/responses/NotFound"
          description: Resource not found
      security:
        - BearerAuth: []
  /endpoints:
    get:
      operationId: listEndpoints
      summary: List webhook endpoints
      description: Returns all active (non-deleted) webhook endpoints.
      tags:
        - Endpoints
      responses:
        "200":
          description: List of endpoints
          content:
            application/json:
              schema:
                allOf:
                  - type: object
                    properties:
                      success:
                        type: boolean
                        const: true
                    required:
                      - success
                      - data
                  - type: object
                    properties:
                      data:
                        type: array
                        items:
                          type: object
                          properties:
                            id:
                              type: string
                              format: uuid
                            org_id:
                              type: string
                              format: uuid
                            url:
                              type:
                                - string
                                - "null"
                            enabled:
                              type: boolean
                            domain_id:
                              type:
                                - string
                                - "null"
                              format: uuid
                              description: Restrict this endpoint to emails from a specific domain
                            rules:
                              type: object
                              description: Endpoint-specific filtering rules
                            created_at:
                              type: string
                              format: date-time
                            updated_at:
                              type: string
                              format: date-time
                            delivery_count:
                              type: integer
                              description: Total webhook deliveries attempted
                            success_count:
                              type: integer
                              description: Successful deliveries
                            failure_count:
                              type: integer
                              description: Failed deliveries
                            consecutive_fails:
                              type: integer
                              description: Current streak of consecutive failures
                            last_delivery_at:
                              type:
                                - string
                                - "null"
                              format: date-time
                            last_success_at:
                              type:
                                - string
                                - "null"
                              format: date-time
                            last_failure_at:
                              type:
                                - string
                                - "null"
                              format: date-time
                            deactivated_at:
                              type:
                                - string
                                - "null"
                              format: date-time
                            kind:
                              type: string
                              enum:
                                - http
                                - function
                              description: "http: deliver to the webhook URL. function: invoke a Primitive Function."
                            function_id:
                              type:
                                - string
                                - "null"
                              format: uuid
                              description: The Function this endpoint invokes, when kind is function.
                            is_route_target:
                              type: boolean
                              description: |
                                When true, this endpoint is reachable only via an explicit recipient
                                route, never as a domain's default destination, and is exempt from
                                the one-endpoint-per-domain rule (so many can share a domain).
                          required:
                            - id
                            - org_id
                            - enabled
                            - rules
                            - created_at
                            - updated_at
                            - delivery_count
                            - success_count
                            - failure_count
                            - consecutive_fails
          headers:
            ratelimit-limit:
              description: Maximum number of requests allowed in the current window.
              schema:
                type: integer
                minimum: 1
                example: 120
            ratelimit-remaining:
              description: Remaining requests in the current window.
              schema:
                type: integer
                minimum: 0
                example: 118
            ratelimit-reset:
              description: Unix timestamp (seconds) when the current window resets.
              schema:
                type: integer
                example: 1700000060
            ratelimit-policy:
              description: Rate-limit policy in `limit;w=seconds` format, e.g. `120;w=60`.
              schema:
                type: string
                example: 120;w=60
        "401":
          $ref: "#/components/responses/Unauthorized"
          description: Invalid or missing API key
      security:
        - BearerAuth: []
    post:
      operationId: createEndpoint
      summary: Create a webhook endpoint
      description: |
        Creates a new webhook endpoint. If a deactivated endpoint
        with the same URL and domain exists, it is reactivated
        instead. Subject to plan limits on the number of active
        endpoints.

        **Signing is account-scoped, not per-endpoint.** This call
        does not return any signing material; every endpoint on the
        account uses the same webhook secret, fetched via
        `GET /account/webhook-secret`. See the API-level "Webhook
        signing" section for the full wire format (header name,
        signed string, hash algo, secret format, tolerance) and a
        language-agnostic verification recipe.

        After creating the endpoint, fire a test delivery against
        it via `POST /endpoints/{id}/test` to confirm your verifier
        accepts the signature.
      tags:
        - Endpoints
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              additionalProperties: false
              properties:
                kind:
                  type: string
                  enum:
                    - http
                    - function
                  default: http
                  description: "http: deliver to a webhook URL (provide url). function: invoke a Primitive Function (provide function_id, omit url)."
                url:
                  type: string
                  minLength: 1
                  description: The webhook URL to deliver events to. Required when kind is http; omit for function endpoints.
                function_id:
                  type: string
                  format: uuid
                  description: The Function to invoke. Required when kind is function.
                enabled:
                  type: boolean
                  default: true
                  description: Whether the endpoint is active
                domain_id:
                  type:
                    - string
                    - "null"
                  format: uuid
                  description: Restrict to emails from a specific domain
                rules:
                  type: object
                  description: Endpoint-specific filtering rules
                is_route_target:
                  type: boolean
                  default: false
                  description: |
                    Create this endpoint as a route-target: reachable only via an
                    explicit recipient route, never a domain's default destination, and
                    exempt from the one-endpoint-per-domain rule.
      responses:
        "201":
          description: Endpoint created (or reactivated)
          content:
            application/json:
              schema:
                allOf:
                  - type: object
                    properties:
                      success:
                        type: boolean
                        const: true
                    required:
                      - success
                      - data
                  - type: object
                    properties:
                      data:
                        type: object
                        properties:
                          id:
                            type: string
                            format: uuid
                          org_id:
                            type: string
                            format: uuid
                          url:
                            type:
                              - string
                              - "null"
                          enabled:
                            type: boolean
                          domain_id:
                            type:
                              - string
                              - "null"
                            format: uuid
                            description: Restrict this endpoint to emails from a specific domain
                          rules:
                            type: object
                            description: Endpoint-specific filtering rules
                          created_at:
                            type: string
                            format: date-time
                          updated_at:
                            type: string
                            format: date-time
                          delivery_count:
                            type: integer
                            description: Total webhook deliveries attempted
                          success_count:
                            type: integer
                            description: Successful deliveries
                          failure_count:
                            type: integer
                            description: Failed deliveries
                          consecutive_fails:
                            type: integer
                            description: Current streak of consecutive failures
                          last_delivery_at:
                            type:
                              - string
                              - "null"
                            format: date-time
                          last_success_at:
                            type:
                              - string
                              - "null"
                            format: date-time
                          last_failure_at:
                            type:
                              - string
                              - "null"
                            format: date-time
                          deactivated_at:
                            type:
                              - string
                              - "null"
                            format: date-time
                          kind:
                            type: string
                            enum:
                              - http
                              - function
                            description: "http: deliver to the webhook URL. function: invoke a Primitive Function."
                          function_id:
                            type:
                              - string
                              - "null"
                            format: uuid
                            description: The Function this endpoint invokes, when kind is function.
                          is_route_target:
                            type: boolean
                            description: |
                              When true, this endpoint is reachable only via an explicit recipient
                              route, never as a domain's default destination, and is exempt from
                              the one-endpoint-per-domain rule (so many can share a domain).
                        required:
                          - id
                          - org_id
                          - enabled
                          - rules
                          - created_at
                          - updated_at
                          - delivery_count
                          - success_count
                          - failure_count
                          - consecutive_fails
        "400":
          $ref: "#/components/responses/ValidationError"
          description: Invalid request parameters
        "401":
          $ref: "#/components/responses/Unauthorized"
          description: Invalid or missing API key
      security:
        - BearerAuth: []
      parameters:
        - name: Idempotency-Key
          in: header
          required: false
          description: Optional client-supplied idempotency key. Retrying a request with the same key returns the original result instead of performing the action a second time; if omitted the server derives one from the canonical payload hash. Safe to retry network failures without duplicating side effects.
          schema:
            type: string
            minLength: 1
            maxLength: 255
  /endpoints/{id}:
    parameters:
      - name: id
        in: path
        required: true
        schema:
          type: string
          format: uuid
        description: Resource UUID
    patch:
      operationId: updateEndpoint
      summary: Update a webhook endpoint
      description: |
        Updates an active webhook endpoint. If the URL is changed, the old
        endpoint is deactivated and a new one is created (or an existing
        deactivated endpoint with the new URL is reactivated).
      tags:
        - Endpoints
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              additionalProperties: false
              properties:
                url:
                  type: string
                  minLength: 1
                  description: New webhook URL (triggers endpoint rotation)
                enabled:
                  type: boolean
                domain_id:
                  type:
                    - string
                    - "null"
                  format: uuid
                rules:
                  type: object
              minProperties: 1
      responses:
        "200":
          description: Updated endpoint
          content:
            application/json:
              schema:
                allOf:
                  - type: object
                    properties:
                      success:
                        type: boolean
                        const: true
                    required:
                      - success
                      - data
                  - type: object
                    properties:
                      data:
                        type: object
                        properties:
                          id:
                            type: string
                            format: uuid
                          org_id:
                            type: string
                            format: uuid
                          url:
                            type:
                              - string
                              - "null"
                          enabled:
                            type: boolean
                          domain_id:
                            type:
                              - string
                              - "null"
                            format: uuid
                            description: Restrict this endpoint to emails from a specific domain
                          rules:
                            type: object
                            description: Endpoint-specific filtering rules
                          created_at:
                            type: string
                            format: date-time
                          updated_at:
                            type: string
                            format: date-time
                          delivery_count:
                            type: integer
                            description: Total webhook deliveries attempted
                          success_count:
                            type: integer
                            description: Successful deliveries
                          failure_count:
                            type: integer
                            description: Failed deliveries
                          consecutive_fails:
                            type: integer
                            description: Current streak of consecutive failures
                          last_delivery_at:
                            type:
                              - string
                              - "null"
                            format: date-time
                          last_success_at:
                            type:
                              - string
                              - "null"
                            format: date-time
                          last_failure_at:
                            type:
                              - string
                              - "null"
                            format: date-time
                          deactivated_at:
                            type:
                              - string
                              - "null"
                            format: date-time
                          kind:
                            type: string
                            enum:
                              - http
                              - function
                            description: "http: deliver to the webhook URL. function: invoke a Primitive Function."
                          function_id:
                            type:
                              - string
                              - "null"
                            format: uuid
                            description: The Function this endpoint invokes, when kind is function.
                          is_route_target:
                            type: boolean
                            description: |
                              When true, this endpoint is reachable only via an explicit recipient
                              route, never as a domain's default destination, and is exempt from
                              the one-endpoint-per-domain rule (so many can share a domain).
                        required:
                          - id
                          - org_id
                          - enabled
                          - rules
                          - created_at
                          - updated_at
                          - delivery_count
                          - success_count
                          - failure_count
                          - consecutive_fails
          headers:
            ratelimit-limit:
              description: Maximum number of requests allowed in the current window.
              schema:
                type: integer
                minimum: 1
                example: 120
            ratelimit-remaining:
              description: Remaining requests in the current window.
              schema:
                type: integer
                minimum: 0
                example: 118
            ratelimit-reset:
              description: Unix timestamp (seconds) when the current window resets.
              schema:
                type: integer
                example: 1700000060
            ratelimit-policy:
              description: Rate-limit policy in `limit;w=seconds` format, e.g. `120;w=60`.
              schema:
                type: string
                example: 120;w=60
        "400":
          $ref: "#/components/responses/ValidationError"
          description: Invalid request parameters
        "401":
          $ref: "#/components/responses/Unauthorized"
          description: Invalid or missing API key
        "404":
          $ref: "#/components/responses/NotFound"
          description: Resource not found
      security:
        - BearerAuth: []
    delete:
      operationId: deleteEndpoint
      summary: Delete a webhook endpoint
      description: Delete a webhook endpoint. Inbound messages are no longer delivered to it.
      tags:
        - Endpoints
      responses:
        "200":
          description: Resource deleted
          content:
            application/json:
              schema:
                allOf:
                  - type: object
                    properties:
                      success:
                        type: boolean
                        const: true
                    required:
                      - success
                      - data
                  - type: object
                    properties:
                      data:
                        type: object
                        properties:
                          deleted:
                            type: boolean
                            const: true
                        required:
                          - deleted
          headers:
            ratelimit-limit:
              description: Maximum number of requests allowed in the current window.
              schema:
                type: integer
                minimum: 1
                example: 120
            ratelimit-remaining:
              description: Remaining requests in the current window.
              schema:
                type: integer
                minimum: 0
                example: 118
            ratelimit-reset:
              description: Unix timestamp (seconds) when the current window resets.
              schema:
                type: integer
                example: 1700000060
            ratelimit-policy:
              description: Rate-limit policy in `limit;w=seconds` format, e.g. `120;w=60`.
              schema:
                type: string
                example: 120;w=60
        "400":
          $ref: "#/components/responses/ValidationError"
          description: Invalid request parameters
        "401":
          $ref: "#/components/responses/Unauthorized"
          description: Invalid or missing API key
        "404":
          $ref: "#/components/responses/NotFound"
          description: Resource not found
      security:
        - BearerAuth: []
  /endpoints/{id}/test:
    parameters:
      - name: id
        in: path
        required: true
        schema:
          type: string
          format: uuid
        description: Resource UUID
    post:
      operationId: testEndpoint
      summary: Send a test webhook
      description: |
        Sends a sample `email.received` event to the endpoint. The request
        includes SSRF protection (private IP rejection and DNS pinning).
        Rate limited to 4 per minute and 30 per hour (non-exempt).
        Successful deliveries and verified-domain endpoints are exempt
        from the rate limit.
      tags:
        - Endpoints
      responses:
        "200":
          description: Test result
          content:
            application/json:
              schema:
                allOf:
                  - type: object
                    properties:
                      success:
                        type: boolean
                        const: true
                    required:
                      - success
                      - data
                  - type: object
                    properties:
                      data:
                        type: object
                        properties:
                          status:
                            type: integer
                            description: HTTP status code returned by the endpoint
                          body:
                            type: string
                            description: Response body (truncated to 1000 characters)
                          signature:
                            type: string
                            description: The signature header value sent (if webhook secret is configured)
                        required:
                          - status
                          - body
          headers:
            ratelimit-limit:
              description: Maximum number of requests allowed in the current window.
              schema:
                type: integer
                minimum: 1
                example: 120
            ratelimit-remaining:
              description: Remaining requests in the current window.
              schema:
                type: integer
                minimum: 0
                example: 118
            ratelimit-reset:
              description: Unix timestamp (seconds) when the current window resets.
              schema:
                type: integer
                example: 1700000060
            ratelimit-policy:
              description: Rate-limit policy in `limit;w=seconds` format, e.g. `120;w=60`.
              schema:
                type: string
                example: 120;w=60
        "400":
          $ref: "#/components/responses/ValidationError"
          description: Invalid request parameters
        "401":
          $ref: "#/components/responses/Unauthorized"
          description: Invalid or missing API key
        "404":
          $ref: "#/components/responses/NotFound"
          description: Resource not found
        "429":
          $ref: "#/components/responses/RateLimited"
          description: Rate limit exceeded
      security:
        - BearerAuth: []
      parameters:
        - name: Idempotency-Key
          in: header
          required: false
          description: Optional client-supplied idempotency key. Retrying a request with the same key returns the original result instead of performing the action a second time; if omitted the server derives one from the canonical payload hash. Safe to retry network failures without duplicating side effects.
          schema:
            type: string
            minLength: 1
            maxLength: 255
  /filters:
    get:
      operationId: listFilters
      summary: List filter rules
      description: Returns all whitelist and blocklist filter rules.
      tags:
        - Filters
      responses:
        "200":
          description: List of filters
          content:
            application/json:
              schema:
                allOf:
                  - type: object
                    properties:
                      success:
                        type: boolean
                        const: true
                    required:
                      - success
                      - data
                  - type: object
                    properties:
                      data:
                        type: array
                        items:
                          type: object
                          properties:
                            id:
                              type: string
                              format: uuid
                            org_id:
                              type: string
                              format: uuid
                            domain_id:
                              type:
                                - string
                                - "null"
                              format: uuid
                              description: If set, filter applies only to this domain
                            type:
                              type: string
                              enum:
                                - whitelist
                                - blocklist
                            pattern:
                              type: string
                              description: Email address or pattern to match (stored lowercase)
                            enabled:
                              type: boolean
                            created_at:
                              type: string
                              format: date-time
                          required:
                            - id
                            - org_id
                            - type
                            - pattern
                            - enabled
                            - created_at
          headers:
            ratelimit-limit:
              description: Maximum number of requests allowed in the current window.
              schema:
                type: integer
                minimum: 1
                example: 120
            ratelimit-remaining:
              description: Remaining requests in the current window.
              schema:
                type: integer
                minimum: 0
                example: 118
            ratelimit-reset:
              description: Unix timestamp (seconds) when the current window resets.
              schema:
                type: integer
                example: 1700000060
            ratelimit-policy:
              description: Rate-limit policy in `limit;w=seconds` format, e.g. `120;w=60`.
              schema:
                type: string
                example: 120;w=60
        "401":
          $ref: "#/components/responses/Unauthorized"
          description: Invalid or missing API key
      security:
        - BearerAuth: []
    post:
      operationId: createFilter
      summary: Create a filter rule
      description: |
        Creates a new whitelist or blocklist filter. Per-domain filters
        require a Pro plan. Patterns are stored as lowercase.
      tags:
        - Filters
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              additionalProperties: false
              properties:
                type:
                  type: string
                  enum:
                    - whitelist
                    - blocklist
                pattern:
                  type: string
                  minLength: 1
                  maxLength: 500
                  description: Email address or pattern to filter
                domain_id:
                  type:
                    - string
                    - "null"
                  format: uuid
                  description: Restrict filter to a specific domain (Pro plan required)
              required:
                - type
                - pattern
      responses:
        "201":
          description: Filter created
          content:
            application/json:
              schema:
                allOf:
                  - type: object
                    properties:
                      success:
                        type: boolean
                        const: true
                    required:
                      - success
                      - data
                  - type: object
                    properties:
                      data:
                        type: object
                        properties:
                          id:
                            type: string
                            format: uuid
                          org_id:
                            type: string
                            format: uuid
                          domain_id:
                            type:
                              - string
                              - "null"
                            format: uuid
                            description: If set, filter applies only to this domain
                          type:
                            type: string
                            enum:
                              - whitelist
                              - blocklist
                          pattern:
                            type: string
                            description: Email address or pattern to match (stored lowercase)
                          enabled:
                            type: boolean
                          created_at:
                            type: string
                            format: date-time
                        required:
                          - id
                          - org_id
                          - type
                          - pattern
                          - enabled
                          - created_at
        "400":
          $ref: "#/components/responses/ValidationError"
          description: Invalid request parameters
        "401":
          $ref: "#/components/responses/Unauthorized"
          description: Invalid or missing API key
        "404":
          $ref: "#/components/responses/NotFound"
          description: Resource not found
      security:
        - BearerAuth: []
      parameters:
        - name: Idempotency-Key
          in: header
          required: false
          description: Optional client-supplied idempotency key. Retrying a request with the same key returns the original result instead of performing the action a second time; if omitted the server derives one from the canonical payload hash. Safe to retry network failures without duplicating side effects.
          schema:
            type: string
            minLength: 1
            maxLength: 255
  /filters/{id}:
    parameters:
      - name: id
        in: path
        required: true
        schema:
          type: string
          format: uuid
        description: Resource UUID
    patch:
      operationId: updateFilter
      summary: Update a filter rule
      description: Toggle a filter's enabled state.
      tags:
        - Filters
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              additionalProperties: false
              properties:
                enabled:
                  type: boolean
              required:
                - enabled
      responses:
        "200":
          description: Updated filter
          content:
            application/json:
              schema:
                allOf:
                  - type: object
                    properties:
                      success:
                        type: boolean
                        const: true
                    required:
                      - success
                      - data
                  - type: object
                    properties:
                      data:
                        type: object
                        properties:
                          id:
                            type: string
                            format: uuid
                          org_id:
                            type: string
                            format: uuid
                          domain_id:
                            type:
                              - string
                              - "null"
                            format: uuid
                            description: If set, filter applies only to this domain
                          type:
                            type: string
                            enum:
                              - whitelist
                              - blocklist
                          pattern:
                            type: string
                            description: Email address or pattern to match (stored lowercase)
                          enabled:
                            type: boolean
                          created_at:
                            type: string
                            format: date-time
                        required:
                          - id
                          - org_id
                          - type
                          - pattern
                          - enabled
                          - created_at
          headers:
            ratelimit-limit:
              description: Maximum number of requests allowed in the current window.
              schema:
                type: integer
                minimum: 1
                example: 120
            ratelimit-remaining:
              description: Remaining requests in the current window.
              schema:
                type: integer
                minimum: 0
                example: 118
            ratelimit-reset:
              description: Unix timestamp (seconds) when the current window resets.
              schema:
                type: integer
                example: 1700000060
            ratelimit-policy:
              description: Rate-limit policy in `limit;w=seconds` format, e.g. `120;w=60`.
              schema:
                type: string
                example: 120;w=60
        "400":
          $ref: "#/components/responses/ValidationError"
          description: Invalid request parameters
        "401":
          $ref: "#/components/responses/Unauthorized"
          description: Invalid or missing API key
        "404":
          $ref: "#/components/responses/NotFound"
          description: Resource not found
      security:
        - BearerAuth: []
    delete:
      operationId: deleteFilter
      summary: Delete a filter rule
      tags:
        - Filters
      responses:
        "200":
          description: Resource deleted
          content:
            application/json:
              schema:
                allOf:
                  - type: object
                    properties:
                      success:
                        type: boolean
                        const: true
                    required:
                      - success
                      - data
                  - type: object
                    properties:
                      data:
                        type: object
                        properties:
                          deleted:
                            type: boolean
                            const: true
                        required:
                          - deleted
          headers:
            ratelimit-limit:
              description: Maximum number of requests allowed in the current window.
              schema:
                type: integer
                minimum: 1
                example: 120
            ratelimit-remaining:
              description: Remaining requests in the current window.
              schema:
                type: integer
                minimum: 0
                example: 118
            ratelimit-reset:
              description: Unix timestamp (seconds) when the current window resets.
              schema:
                type: integer
                example: 1700000060
            ratelimit-policy:
              description: Rate-limit policy in `limit;w=seconds` format, e.g. `120;w=60`.
              schema:
                type: string
                example: 120;w=60
        "400":
          $ref: "#/components/responses/ValidationError"
          description: Invalid request parameters
        "401":
          $ref: "#/components/responses/Unauthorized"
          description: Invalid or missing API key
        "404":
          $ref: "#/components/responses/NotFound"
          description: Resource not found
      security:
        - BearerAuth: []
      description: Delete an inbound routing filter rule. Messages already processed by the rule are unaffected.
  /wake/schedules:
    get:
      operationId: listWakeSchedules
      summary: List wake schedules
      description: Returns the org's wake.dispatch schedules.
      tags:
        - Wake
      responses:
        "200":
          description: List of wake schedules
          content:
            application/json:
              schema:
                allOf:
                  - type: object
                    properties:
                      success:
                        type: boolean
                        const: true
                    required:
                      - success
                      - data
                  - type: object
                    properties:
                      data:
                        type: array
                        items:
                          type: object
                          description: A cron schedule that sends a wake.dispatch command to a function.
                          properties:
                            id:
                              type: string
                              format: uuid
                            from_address:
                              type:
                                - string
                                - "null"
                              description: The sending identity the wake is signed as.
                            target_address:
                              type: string
                              description: The function address the wake is delivered to.
                            command:
                              type: string
                            args:
                              type: object
                              additionalProperties: true
                            cron_expr:
                              type: string
                              description: 5-field cron expression.
                            timezone:
                              type: string
                              description: IANA timezone the cron is evaluated in.
                            next_run_at:
                              type: string
                              format: date-time
                            last_run_at:
                              type:
                                - string
                                - "null"
                              format: date-time
                            enabled:
                              type: boolean
                            note:
                              type:
                                - string
                                - "null"
                            created_at:
                              type: string
                              format: date-time
                            updated_at:
                              type: string
                              format: date-time
                          required:
                            - id
                            - target_address
                            - command
                            - cron_expr
                            - timezone
                            - next_run_at
                            - enabled
                            - created_at
                            - updated_at
          headers:
            ratelimit-limit:
              description: Maximum number of requests allowed in the current window.
              schema:
                type: integer
                minimum: 1
                example: 120
            ratelimit-remaining:
              description: Remaining requests in the current window.
              schema:
                type: integer
                minimum: 0
                example: 118
            ratelimit-reset:
              description: Unix timestamp (seconds) when the current window resets.
              schema:
                type: integer
                example: 1700000060
            ratelimit-policy:
              description: Rate-limit policy in `limit;w=seconds` format, e.g. `120;w=60`.
              schema:
                type: string
                example: 120;w=60
        "401":
          $ref: "#/components/responses/Unauthorized"
          description: Invalid or missing API key
      security:
        - BearerAuth: []
    post:
      operationId: createWakeSchedule
      summary: Create a wake schedule
      description: |
        Create a cron schedule that sends a wake.dispatch command to one of your
        own function addresses. `from` and `to` must differ (no self-dispatch);
        the cron expression and IANA timezone are validated and the first fire
        time is computed without firing immediately.
      tags:
        - Wake
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              additionalProperties: false
              properties:
                from_address:
                  type: string
                  description: Sending identity (must be a domain the org can sign).
                target_address:
                  type: string
                  description: Your function address (must differ from from_address).
                command:
                  type: string
                  minLength: 1
                  maxLength: 200
                args:
                  type: object
                  additionalProperties: true
                  description: Optional JSON object passed through to the woken function.
                cron_expr:
                  type: string
                  minLength: 1
                  maxLength: 120
                timezone:
                  type: string
                  minLength: 1
                  maxLength: 64
                  default: UTC
                note:
                  type: string
                  maxLength: 2000
              required:
                - from_address
                - target_address
                - command
                - cron_expr
      responses:
        "201":
          description: Wake schedule created
          content:
            application/json:
              schema:
                allOf:
                  - type: object
                    properties:
                      success:
                        type: boolean
                        const: true
                    required:
                      - success
                      - data
                  - type: object
                    properties:
                      data:
                        type: object
                        description: A cron schedule that sends a wake.dispatch command to a function.
                        properties:
                          id:
                            type: string
                            format: uuid
                          from_address:
                            type:
                              - string
                              - "null"
                            description: The sending identity the wake is signed as.
                          target_address:
                            type: string
                            description: The function address the wake is delivered to.
                          command:
                            type: string
                          args:
                            type: object
                            additionalProperties: true
                          cron_expr:
                            type: string
                            description: 5-field cron expression.
                          timezone:
                            type: string
                            description: IANA timezone the cron is evaluated in.
                          next_run_at:
                            type: string
                            format: date-time
                          last_run_at:
                            type:
                              - string
                              - "null"
                            format: date-time
                          enabled:
                            type: boolean
                          note:
                            type:
                              - string
                              - "null"
                          created_at:
                            type: string
                            format: date-time
                          updated_at:
                            type: string
                            format: date-time
                        required:
                          - id
                          - target_address
                          - command
                          - cron_expr
                          - timezone
                          - next_run_at
                          - enabled
                          - created_at
                          - updated_at
        "400":
          $ref: "#/components/responses/ValidationError"
          description: Invalid request parameters
        "401":
          $ref: "#/components/responses/Unauthorized"
          description: Invalid or missing API key
      security:
        - BearerAuth: []
      parameters:
        - name: Idempotency-Key
          in: header
          required: false
          description: Optional client-supplied idempotency key. Retrying a request with the same key returns the original result instead of performing the action a second time; if omitted the server derives one from the canonical payload hash. Safe to retry network failures without duplicating side effects.
          schema:
            type: string
            minLength: 1
            maxLength: 255
  /wake/schedules/{id}:
    parameters:
      - name: id
        in: path
        required: true
        schema:
          type: string
          format: uuid
        description: Resource UUID
    get:
      operationId: getWakeSchedule
      summary: Get a wake schedule
      tags:
        - Wake
      responses:
        "200":
          description: The wake schedule
          content:
            application/json:
              schema:
                allOf:
                  - type: object
                    properties:
                      success:
                        type: boolean
                        const: true
                    required:
                      - success
                      - data
                  - type: object
                    properties:
                      data:
                        type: object
                        description: A cron schedule that sends a wake.dispatch command to a function.
                        properties:
                          id:
                            type: string
                            format: uuid
                          from_address:
                            type:
                              - string
                              - "null"
                            description: The sending identity the wake is signed as.
                          target_address:
                            type: string
                            description: The function address the wake is delivered to.
                          command:
                            type: string
                          args:
                            type: object
                            additionalProperties: true
                          cron_expr:
                            type: string
                            description: 5-field cron expression.
                          timezone:
                            type: string
                            description: IANA timezone the cron is evaluated in.
                          next_run_at:
                            type: string
                            format: date-time
                          last_run_at:
                            type:
                              - string
                              - "null"
                            format: date-time
                          enabled:
                            type: boolean
                          note:
                            type:
                              - string
                              - "null"
                          created_at:
                            type: string
                            format: date-time
                          updated_at:
                            type: string
                            format: date-time
                        required:
                          - id
                          - target_address
                          - command
                          - cron_expr
                          - timezone
                          - next_run_at
                          - enabled
                          - created_at
                          - updated_at
          headers:
            ratelimit-limit:
              description: Maximum number of requests allowed in the current window.
              schema:
                type: integer
                minimum: 1
                example: 120
            ratelimit-remaining:
              description: Remaining requests in the current window.
              schema:
                type: integer
                minimum: 0
                example: 118
            ratelimit-reset:
              description: Unix timestamp (seconds) when the current window resets.
              schema:
                type: integer
                example: 1700000060
            ratelimit-policy:
              description: Rate-limit policy in `limit;w=seconds` format, e.g. `120;w=60`.
              schema:
                type: string
                example: 120;w=60
        "401":
          $ref: "#/components/responses/Unauthorized"
          description: Invalid or missing API key
        "404":
          $ref: "#/components/responses/NotFound"
          description: Resource not found
      security:
        - BearerAuth: []
      description: Get a wake schedule
    patch:
      operationId: updateWakeSchedule
      summary: Update a wake schedule
      description: |
        Update a schedule's command, args, cadence, addresses, note, or enabled
        state. Changing the cadence (or re-enabling) recomputes the next fire time.
      tags:
        - Wake
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              additionalProperties: false
              properties:
                enabled:
                  type: boolean
                command:
                  type: string
                  minLength: 1
                  maxLength: 200
                args:
                  type: object
                  additionalProperties: true
                cron_expr:
                  type: string
                  minLength: 1
                  maxLength: 120
                timezone:
                  type: string
                  minLength: 1
                  maxLength: 64
                from_address:
                  type: string
                target_address:
                  type: string
                note:
                  type:
                    - string
                    - "null"
                  maxLength: 2000
      responses:
        "200":
          description: Updated wake schedule
          content:
            application/json:
              schema:
                allOf:
                  - type: object
                    properties:
                      success:
                        type: boolean
                        const: true
                    required:
                      - success
                      - data
                  - type: object
                    properties:
                      data:
                        type: object
                        description: A cron schedule that sends a wake.dispatch command to a function.
                        properties:
                          id:
                            type: string
                            format: uuid
                          from_address:
                            type:
                              - string
                              - "null"
                            description: The sending identity the wake is signed as.
                          target_address:
                            type: string
                            description: The function address the wake is delivered to.
                          command:
                            type: string
                          args:
                            type: object
                            additionalProperties: true
                          cron_expr:
                            type: string
                            description: 5-field cron expression.
                          timezone:
                            type: string
                            description: IANA timezone the cron is evaluated in.
                          next_run_at:
                            type: string
                            format: date-time
                          last_run_at:
                            type:
                              - string
                              - "null"
                            format: date-time
                          enabled:
                            type: boolean
                          note:
                            type:
                              - string
                              - "null"
                          created_at:
                            type: string
                            format: date-time
                          updated_at:
                            type: string
                            format: date-time
                        required:
                          - id
                          - target_address
                          - command
                          - cron_expr
                          - timezone
                          - next_run_at
                          - enabled
                          - created_at
                          - updated_at
          headers:
            ratelimit-limit:
              description: Maximum number of requests allowed in the current window.
              schema:
                type: integer
                minimum: 1
                example: 120
            ratelimit-remaining:
              description: Remaining requests in the current window.
              schema:
                type: integer
                minimum: 0
                example: 118
            ratelimit-reset:
              description: Unix timestamp (seconds) when the current window resets.
              schema:
                type: integer
                example: 1700000060
            ratelimit-policy:
              description: Rate-limit policy in `limit;w=seconds` format, e.g. `120;w=60`.
              schema:
                type: string
                example: 120;w=60
        "400":
          $ref: "#/components/responses/ValidationError"
          description: Invalid request parameters
        "401":
          $ref: "#/components/responses/Unauthorized"
          description: Invalid or missing API key
        "404":
          $ref: "#/components/responses/NotFound"
          description: Resource not found
      security:
        - BearerAuth: []
    delete:
      operationId: deleteWakeSchedule
      summary: Delete a wake schedule
      tags:
        - Wake
      responses:
        "200":
          description: Resource deleted
          content:
            application/json:
              schema:
                allOf:
                  - type: object
                    properties:
                      success:
                        type: boolean
                        const: true
                    required:
                      - success
                      - data
                  - type: object
                    properties:
                      data:
                        type: object
                        properties:
                          deleted:
                            type: boolean
                            const: true
                        required:
                          - deleted
          headers:
            ratelimit-limit:
              description: Maximum number of requests allowed in the current window.
              schema:
                type: integer
                minimum: 1
                example: 120
            ratelimit-remaining:
              description: Remaining requests in the current window.
              schema:
                type: integer
                minimum: 0
                example: 118
            ratelimit-reset:
              description: Unix timestamp (seconds) when the current window resets.
              schema:
                type: integer
                example: 1700000060
            ratelimit-policy:
              description: Rate-limit policy in `limit;w=seconds` format, e.g. `120;w=60`.
              schema:
                type: string
                example: 120;w=60
        "401":
          $ref: "#/components/responses/Unauthorized"
          description: Invalid or missing API key
        "404":
          $ref: "#/components/responses/NotFound"
          description: Resource not found
      security:
        - BearerAuth: []
      description: Delete a wake schedule
  /wake/schedules/{id}/run:
    parameters:
      - name: id
        in: path
        required: true
        schema:
          type: string
          format: uuid
        description: Resource UUID
    post:
      operationId: runWakeSchedule
      summary: Run a wake schedule now
      description: |
        Fire the schedule immediately, sending one wake.dispatch via the same
        signed-send path as a scheduled fire. Does not change the schedule's next
        fire time.
      tags:
        - Wake
      responses:
        "200":
          description: The wake interaction that was emitted
          content:
            application/json:
              schema:
                allOf:
                  - type: object
                    properties:
                      success:
                        type: boolean
                        const: true
                    required:
                      - success
                      - data
                  - type: object
                    properties:
                      data:
                        type: object
                        additionalProperties: true
          headers:
            ratelimit-limit:
              description: Maximum number of requests allowed in the current window.
              schema:
                type: integer
                minimum: 1
                example: 120
            ratelimit-remaining:
              description: Remaining requests in the current window.
              schema:
                type: integer
                minimum: 0
                example: 118
            ratelimit-reset:
              description: Unix timestamp (seconds) when the current window resets.
              schema:
                type: integer
                example: 1700000060
            ratelimit-policy:
              description: Rate-limit policy in `limit;w=seconds` format, e.g. `120;w=60`.
              schema:
                type: string
                example: 120;w=60
        "400":
          $ref: "#/components/responses/ValidationError"
          description: Invalid request parameters
        "401":
          $ref: "#/components/responses/Unauthorized"
          description: Invalid or missing API key
        "404":
          $ref: "#/components/responses/NotFound"
          description: Resource not found
      security:
        - BearerAuth: []
      parameters:
        - name: Idempotency-Key
          in: header
          required: false
          description: Optional client-supplied idempotency key. Retrying a request with the same key returns the original result instead of performing the action a second time; if omitted the server derives one from the canonical payload hash. Safe to retry network failures without duplicating side effects.
          schema:
            type: string
            minLength: 1
            maxLength: 255
  /wake/authorizations:
    get:
      operationId: listWakeAuthorizations
      summary: List wake authorizations
      description: |
        Returns the per-target allowlist grants that authorize which senders may
        wake a function. Optionally filter by the target endpoint.
      tags:
        - Wake
      parameters:
        - name: recipient_endpoint_id
          in: query
          required: false
          schema:
            type: string
            format: uuid
          description: Only return grants for this target endpoint
      responses:
        "200":
          description: List of wake authorizations
          content:
            application/json:
              schema:
                allOf:
                  - type: object
                    properties:
                      success:
                        type: boolean
                        const: true
                    required:
                      - success
                      - data
                  - type: object
                    properties:
                      data:
                        type: array
                        items:
                          type: object
                          description: A per-target allowlist grant authorizing a sender to wake a function.
                          properties:
                            id:
                              type: string
                              format: uuid
                            recipient_endpoint_id:
                              type: string
                              format: uuid
                            allowed_sender_domain:
                              type: string
                            allowed_sender_address:
                              type:
                                - string
                                - "null"
                            allowed_commands:
                              type:
                                - array
                                - "null"
                              items:
                                type: string
                            enabled:
                              type: boolean
                            note:
                              type:
                                - string
                                - "null"
                            created_at:
                              type: string
                              format: date-time
                          required:
                            - id
                            - recipient_endpoint_id
                            - allowed_sender_domain
                            - enabled
                            - created_at
          headers:
            ratelimit-limit:
              description: Maximum number of requests allowed in the current window.
              schema:
                type: integer
                minimum: 1
                example: 120
            ratelimit-remaining:
              description: Remaining requests in the current window.
              schema:
                type: integer
                minimum: 0
                example: 118
            ratelimit-reset:
              description: Unix timestamp (seconds) when the current window resets.
              schema:
                type: integer
                example: 1700000060
            ratelimit-policy:
              description: Rate-limit policy in `limit;w=seconds` format, e.g. `120;w=60`.
              schema:
                type: string
                example: 120;w=60
        "400":
          $ref: "#/components/responses/ValidationError"
          description: Invalid request parameters
        "401":
          $ref: "#/components/responses/Unauthorized"
          description: Invalid or missing API key
      security:
        - BearerAuth: []
    post:
      operationId: createWakeAuthorization
      summary: Create a wake authorization
      description: |
        Grant a sender domain (and optionally a specific address and command set)
        permission to wake a target function. The domain must be fully-qualified.
      tags:
        - Wake
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              additionalProperties: false
              properties:
                recipient_endpoint_id:
                  type: string
                  format: uuid
                allowed_sender_domain:
                  type: string
                  minLength: 1
                  maxLength: 253
                  description: Fully-qualified sender domain (at least two labels).
                allowed_sender_address:
                  type:
                    - string
                    - "null"
                  description: Optional specific sender address to pin the grant to.
                allowed_commands:
                  type:
                    - array
                    - "null"
                  maxItems: 64
                  items:
                    type: string
                    minLength: 1
                    maxLength: 200
                  description: Optional command allowlist; null = any command.
                note:
                  type: string
                  maxLength: 2000
              required:
                - recipient_endpoint_id
                - allowed_sender_domain
      responses:
        "201":
          description: Wake authorization created
          content:
            application/json:
              schema:
                allOf:
                  - type: object
                    properties:
                      success:
                        type: boolean
                        const: true
                    required:
                      - success
                      - data
                  - type: object
                    properties:
                      data:
                        type: object
                        description: A per-target allowlist grant authorizing a sender to wake a function.
                        properties:
                          id:
                            type: string
                            format: uuid
                          recipient_endpoint_id:
                            type: string
                            format: uuid
                          allowed_sender_domain:
                            type: string
                          allowed_sender_address:
                            type:
                              - string
                              - "null"
                          allowed_commands:
                            type:
                              - array
                              - "null"
                            items:
                              type: string
                          enabled:
                            type: boolean
                          note:
                            type:
                              - string
                              - "null"
                          created_at:
                            type: string
                            format: date-time
                        required:
                          - id
                          - recipient_endpoint_id
                          - allowed_sender_domain
                          - enabled
                          - created_at
        "400":
          $ref: "#/components/responses/ValidationError"
          description: Invalid request parameters
        "401":
          $ref: "#/components/responses/Unauthorized"
          description: Invalid or missing API key
        "404":
          $ref: "#/components/responses/NotFound"
          description: Resource not found
        "409":
          $ref: "#/components/responses/Conflict"
          description: The request conflicts with the current state of the resource
      security:
        - BearerAuth: []
      parameters:
        - name: Idempotency-Key
          in: header
          required: false
          description: Optional client-supplied idempotency key. Retrying a request with the same key returns the original result instead of performing the action a second time; if omitted the server derives one from the canonical payload hash. Safe to retry network failures without duplicating side effects.
          schema:
            type: string
            minLength: 1
            maxLength: 255
  /wake/authorizations/{id}:
    parameters:
      - name: id
        in: path
        required: true
        schema:
          type: string
          format: uuid
        description: Resource UUID
    patch:
      operationId: updateWakeAuthorization
      summary: Update a wake authorization
      description: Toggle a wake authorization's enabled state.
      tags:
        - Wake
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              additionalProperties: false
              properties:
                enabled:
                  type: boolean
              required:
                - enabled
      responses:
        "200":
          description: Updated wake authorization
          content:
            application/json:
              schema:
                allOf:
                  - type: object
                    properties:
                      success:
                        type: boolean
                        const: true
                    required:
                      - success
                      - data
                  - type: object
                    properties:
                      data:
                        type: object
                        description: A per-target allowlist grant authorizing a sender to wake a function.
                        properties:
                          id:
                            type: string
                            format: uuid
                          recipient_endpoint_id:
                            type: string
                            format: uuid
                          allowed_sender_domain:
                            type: string
                          allowed_sender_address:
                            type:
                              - string
                              - "null"
                          allowed_commands:
                            type:
                              - array
                              - "null"
                            items:
                              type: string
                          enabled:
                            type: boolean
                          note:
                            type:
                              - string
                              - "null"
                          created_at:
                            type: string
                            format: date-time
                        required:
                          - id
                          - recipient_endpoint_id
                          - allowed_sender_domain
                          - enabled
                          - created_at
          headers:
            ratelimit-limit:
              description: Maximum number of requests allowed in the current window.
              schema:
                type: integer
                minimum: 1
                example: 120
            ratelimit-remaining:
              description: Remaining requests in the current window.
              schema:
                type: integer
                minimum: 0
                example: 118
            ratelimit-reset:
              description: Unix timestamp (seconds) when the current window resets.
              schema:
                type: integer
                example: 1700000060
            ratelimit-policy:
              description: Rate-limit policy in `limit;w=seconds` format, e.g. `120;w=60`.
              schema:
                type: string
                example: 120;w=60
        "400":
          $ref: "#/components/responses/ValidationError"
          description: Invalid request parameters
        "401":
          $ref: "#/components/responses/Unauthorized"
          description: Invalid or missing API key
        "404":
          $ref: "#/components/responses/NotFound"
          description: Resource not found
      security:
        - BearerAuth: []
    delete:
      operationId: deleteWakeAuthorization
      summary: Delete a wake authorization
      tags:
        - Wake
      responses:
        "200":
          description: Resource deleted
          content:
            application/json:
              schema:
                allOf:
                  - type: object
                    properties:
                      success:
                        type: boolean
                        const: true
                    required:
                      - success
                      - data
                  - type: object
                    properties:
                      data:
                        type: object
                        properties:
                          deleted:
                            type: boolean
                            const: true
                        required:
                          - deleted
          headers:
            ratelimit-limit:
              description: Maximum number of requests allowed in the current window.
              schema:
                type: integer
                minimum: 1
                example: 120
            ratelimit-remaining:
              description: Remaining requests in the current window.
              schema:
                type: integer
                minimum: 0
                example: 118
            ratelimit-reset:
              description: Unix timestamp (seconds) when the current window resets.
              schema:
                type: integer
                example: 1700000060
            ratelimit-policy:
              description: Rate-limit policy in `limit;w=seconds` format, e.g. `120;w=60`.
              schema:
                type: string
                example: 120;w=60
        "401":
          $ref: "#/components/responses/Unauthorized"
          description: Invalid or missing API key
        "404":
          $ref: "#/components/responses/NotFound"
          description: Resource not found
      security:
        - BearerAuth: []
      description: Delete a wake authorization
  /wake/dispatches:
    get:
      operationId: listWakeDispatches
      summary: List recent wake dispatches
      description: Read-only audit of recent wake.dispatch interactions for the org.
      tags:
        - Wake
      parameters:
        - name: limit
          in: query
          required: false
          schema:
            type: integer
            minimum: 1
            maximum: 200
            default: 50
          description: Maximum number of rows to return (1-200, default 50)
      responses:
        "200":
          description: List of recent wake dispatches
          content:
            application/json:
              schema:
                allOf:
                  - type: object
                    properties:
                      success:
                        type: boolean
                        const: true
                    required:
                      - success
                      - data
                  - type: object
                    properties:
                      data:
                        type: array
                        items:
                          type: object
                          description: A recorded wake.dispatch interaction (audit row).
                          properties:
                            id:
                              type: string
                              format: uuid
                            wire_id:
                              type: string
                            role:
                              type: string
                            state:
                              type: string
                            outcome:
                              type:
                                - string
                                - "null"
                            awaiting:
                              type:
                                - string
                                - "null"
                            counterparty_address:
                              type: string
                            our_address:
                              type: string
                            step_count:
                              type: integer
                            created_at:
                              type: string
                              format: date-time
                            completed_at:
                              type:
                                - string
                                - "null"
                              format: date-time
                          required:
                            - id
                            - wire_id
                            - role
                            - state
                            - counterparty_address
                            - our_address
                            - created_at
          headers:
            ratelimit-limit:
              description: Maximum number of requests allowed in the current window.
              schema:
                type: integer
                minimum: 1
                example: 120
            ratelimit-remaining:
              description: Remaining requests in the current window.
              schema:
                type: integer
                minimum: 0
                example: 118
            ratelimit-reset:
              description: Unix timestamp (seconds) when the current window resets.
              schema:
                type: integer
                example: 1700000060
            ratelimit-policy:
              description: Rate-limit policy in `limit;w=seconds` format, e.g. `120;w=60`.
              schema:
                type: string
                example: 120;w=60
        "401":
          $ref: "#/components/responses/Unauthorized"
          description: Invalid or missing API key
      security:
        - BearerAuth: []
  /routes:
    get:
      operationId: listRoutes
      summary: List recipient routes
      description: |
        Returns the org's recipient routing rules in evaluation order. Each rule
        binds a recipient address pattern to one endpoint; inbound mail resolves
        to a single destination at delivery time.
      tags:
        - Routes
      responses:
        "200":
          description: List of routes
          content:
            application/json:
              schema:
                allOf:
                  - type: object
                    properties:
                      success:
                        type: boolean
                        const: true
                    required:
                      - success
                      - data
                  - type: object
                    properties:
                      data:
                        type: array
                        items:
                          type: object
                          description: A recipient routing rule binding an address pattern to one endpoint.
                          properties:
                            id:
                              type: string
                              format: uuid
                            org_id:
                              type: string
                              format: uuid
                            domain_id:
                              type:
                                - string
                                - "null"
                              format: uuid
                              description: Domain the route is scoped to; null = org-wide.
                            match_type:
                              type: string
                              enum:
                                - exact
                                - wildcard
                                - regex
                            pattern:
                              type: string
                              description: The recipient address pattern (an exact address or a wildcard).
                            pattern_norm:
                              type:
                                - string
                                - "null"
                              description: Normalized pattern used for matching.
                            endpoint_id:
                              type: string
                              format: uuid
                              description: The endpoint inbound mail matching this rule is delivered to.
                            priority:
                              type: integer
                              description: Evaluation order within a scope; lower is checked first.
                            enabled:
                              type: boolean
                            match_count:
                              type: string
                              description: How many emails have matched this rule (a bigint, returned as a string).
                            last_matched_at:
                              type:
                                - string
                                - "null"
                              format: date-time
                            created_at:
                              type: string
                              format: date-time
                          required:
                            - id
          headers:
            ratelimit-limit:
              description: Maximum number of requests allowed in the current window.
              schema:
                type: integer
                minimum: 1
                example: 120
            ratelimit-remaining:
              description: Remaining requests in the current window.
              schema:
                type: integer
                minimum: 0
                example: 118
            ratelimit-reset:
              description: Unix timestamp (seconds) when the current window resets.
              schema:
                type: integer
                example: 1700000060
            ratelimit-policy:
              description: Rate-limit policy in `limit;w=seconds` format, e.g. `120;w=60`.
              schema:
                type: string
                example: 120;w=60
        "401":
          $ref: "#/components/responses/Unauthorized"
          description: Invalid or missing API key
      security:
        - BearerAuth: []
    post:
      operationId: createRoute
      summary: Create a recipient route
      description: |
        Binds a recipient pattern to a destination. Provide exactly one of
        `endpoint_id` (an existing endpoint) or `function_id`. With `function_id`,
        a dedicated route-target endpoint is minted for that function in the same
        transaction, enabling per-address function routing (e.g.
        `alice@acme.com -> functionA`).
      tags:
        - Routes
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              additionalProperties: false
              description: |
                Provide exactly one of `endpoint_id` or `function_id`. With `function_id`,
                a route-target endpoint is minted for that function and the route is bound
                to it in one transaction.
              properties:
                match_type:
                  type: string
                  enum:
                    - exact
                    - wildcard
                    - regex
                pattern:
                  type: string
                  minLength: 1
                  maxLength: 512
                endpoint_id:
                  type: string
                  format: uuid
                  description: An existing endpoint to route to. Mutually exclusive with function_id.
                function_id:
                  type: string
                  format: uuid
                  description: Route to this function, minting its route-target endpoint if needed. Mutually exclusive with endpoint_id.
                domain_id:
                  type:
                    - string
                    - "null"
                  format: uuid
                  description: Scope the route to a domain; defaults to the pattern's domain.
                priority:
                  type: integer
                  minimum: 0
                  maximum: 1000000
                enabled:
                  type: boolean
              required:
                - match_type
                - pattern
      responses:
        "201":
          description: Route created
          content:
            application/json:
              schema:
                allOf:
                  - type: object
                    properties:
                      success:
                        type: boolean
                        const: true
                    required:
                      - success
                      - data
                  - type: object
                    properties:
                      data:
                        type: object
                        description: A recipient routing rule binding an address pattern to one endpoint.
                        properties:
                          id:
                            type: string
                            format: uuid
                          org_id:
                            type: string
                            format: uuid
                          domain_id:
                            type:
                              - string
                              - "null"
                            format: uuid
                            description: Domain the route is scoped to; null = org-wide.
                          match_type:
                            type: string
                            enum:
                              - exact
                              - wildcard
                              - regex
                          pattern:
                            type: string
                            description: The recipient address pattern (an exact address or a wildcard).
                          pattern_norm:
                            type:
                              - string
                              - "null"
                            description: Normalized pattern used for matching.
                          endpoint_id:
                            type: string
                            format: uuid
                            description: The endpoint inbound mail matching this rule is delivered to.
                          priority:
                            type: integer
                            description: Evaluation order within a scope; lower is checked first.
                          enabled:
                            type: boolean
                          match_count:
                            type: string
                            description: How many emails have matched this rule (a bigint, returned as a string).
                          last_matched_at:
                            type:
                              - string
                              - "null"
                            format: date-time
                          created_at:
                            type: string
                            format: date-time
                        required:
                          - id
        "400":
          $ref: "#/components/responses/ValidationError"
          description: Invalid request parameters
        "401":
          $ref: "#/components/responses/Unauthorized"
          description: Invalid or missing API key
        "403":
          $ref: "#/components/responses/Forbidden"
          description: Authenticated caller lacks permission for the operation
        "409":
          $ref: "#/components/responses/Conflict"
          description: The request conflicts with the current state of the resource
      security:
        - BearerAuth: []
      parameters:
        - name: Idempotency-Key
          in: header
          required: false
          description: Optional client-supplied idempotency key. Retrying a request with the same key returns the original result instead of performing the action a second time; if omitted the server derives one from the canonical payload hash. Safe to retry network failures without duplicating side effects.
          schema:
            type: string
            minLength: 1
            maxLength: 255
  /routes/reorder:
    post:
      operationId: reorderRoutes
      summary: Reorder recipient routes
      description: Update the priority of one or more routes in a single call.
      tags:
        - Routes
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              additionalProperties: false
              properties:
                updates:
                  type: array
                  minItems: 1
                  maxItems: 1000
                  items:
                    type: object
                    additionalProperties: false
                    properties:
                      id:
                        type: string
                        format: uuid
                      priority:
                        type: integer
                        minimum: 0
                        maximum: 1000000
                    required:
                      - id
                      - priority
              required:
                - updates
      responses:
        "200":
          description: Updated route list
          content:
            application/json:
              schema:
                allOf:
                  - type: object
                    properties:
                      success:
                        type: boolean
                        const: true
                    required:
                      - success
                      - data
                  - type: object
                    properties:
                      data:
                        type: array
                        items:
                          type: object
                          description: A recipient routing rule binding an address pattern to one endpoint.
                          properties:
                            id:
                              type: string
                              format: uuid
                            org_id:
                              type: string
                              format: uuid
                            domain_id:
                              type:
                                - string
                                - "null"
                              format: uuid
                              description: Domain the route is scoped to; null = org-wide.
                            match_type:
                              type: string
                              enum:
                                - exact
                                - wildcard
                                - regex
                            pattern:
                              type: string
                              description: The recipient address pattern (an exact address or a wildcard).
                            pattern_norm:
                              type:
                                - string
                                - "null"
                              description: Normalized pattern used for matching.
                            endpoint_id:
                              type: string
                              format: uuid
                              description: The endpoint inbound mail matching this rule is delivered to.
                            priority:
                              type: integer
                              description: Evaluation order within a scope; lower is checked first.
                            enabled:
                              type: boolean
                            match_count:
                              type: string
                              description: How many emails have matched this rule (a bigint, returned as a string).
                            last_matched_at:
                              type:
                                - string
                                - "null"
                              format: date-time
                            created_at:
                              type: string
                              format: date-time
                          required:
                            - id
          headers:
            ratelimit-limit:
              description: Maximum number of requests allowed in the current window.
              schema:
                type: integer
                minimum: 1
                example: 120
            ratelimit-remaining:
              description: Remaining requests in the current window.
              schema:
                type: integer
                minimum: 0
                example: 118
            ratelimit-reset:
              description: Unix timestamp (seconds) when the current window resets.
              schema:
                type: integer
                example: 1700000060
            ratelimit-policy:
              description: Rate-limit policy in `limit;w=seconds` format, e.g. `120;w=60`.
              schema:
                type: string
                example: 120;w=60
        "400":
          $ref: "#/components/responses/ValidationError"
          description: Invalid request parameters
        "401":
          $ref: "#/components/responses/Unauthorized"
          description: Invalid or missing API key
      security:
        - BearerAuth: []
      parameters:
        - name: Idempotency-Key
          in: header
          required: false
          description: Optional client-supplied idempotency key. Retrying a request with the same key returns the original result instead of performing the action a second time; if omitted the server derives one from the canonical payload hash. Safe to retry network failures without duplicating side effects.
          schema:
            type: string
            minLength: 1
            maxLength: 255
  /routes/simulate:
    post:
      operationId: simulateRoute
      summary: Simulate routing for a recipient
      description: |
        Resolves where an inbound email to `recipient` would be delivered, with a
        trace of every rule evaluated and why. Read-only; creates nothing.
      tags:
        - Routes
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              additionalProperties: false
              properties:
                recipient:
                  type: string
                  minLength: 1
                  maxLength: 320
                event_type:
                  type: string
                  minLength: 1
                  maxLength: 100
                  description: Event type to model; defaults to email.received.
              required:
                - recipient
      responses:
        "200":
          description: Routing decision
          content:
            application/json:
              schema:
                allOf:
                  - type: object
                    properties:
                      success:
                        type: boolean
                        const: true
                    required:
                      - success
                      - data
                  - type: object
                    properties:
                      data:
                        type: object
                        description: Where an inbound email to the recipient would be delivered, and why.
                        properties:
                          outcome:
                            type: string
                            enum:
                              - matched
                              - defaulted
                              - none
                          recipient:
                            type: string
                          endpoint_id:
                            type:
                              - string
                              - "null"
                          matched_route_id:
                            type:
                              - string
                              - "null"
                          matched_tier:
                            type:
                              - string
                              - "null"
                            enum:
                              - exact
                              - wildcard
                              - regex
                              - null
                          matched_pattern:
                            type:
                              - string
                              - "null"
                          default_scope:
                            type:
                              - string
                              - "null"
                            enum:
                              - domain
                              - org
                              - null
                          evaluated:
                            type: array
                            items:
                              type: object
                              properties:
                                route_id:
                                  type: string
                                tier:
                                  type: string
                                  enum:
                                    - exact
                                    - wildcard
                                    - regex
                                pattern:
                                  type: string
                                result:
                                  type: string
                                  enum:
                                    - hit
                                    - miss
                                    - skipped
                                    - error
                                reason:
                                  type: string
                              required:
                                - route_id
                                - tier
                                - pattern
                                - result
                          truncated:
                            type: boolean
                        required:
                          - outcome
                          - recipient
                          - endpoint_id
                          - matched_route_id
                          - matched_tier
                          - matched_pattern
                          - default_scope
                          - evaluated
                          - truncated
          headers:
            ratelimit-limit:
              description: Maximum number of requests allowed in the current window.
              schema:
                type: integer
                minimum: 1
                example: 120
            ratelimit-remaining:
              description: Remaining requests in the current window.
              schema:
                type: integer
                minimum: 0
                example: 118
            ratelimit-reset:
              description: Unix timestamp (seconds) when the current window resets.
              schema:
                type: integer
                example: 1700000060
            ratelimit-policy:
              description: Rate-limit policy in `limit;w=seconds` format, e.g. `120;w=60`.
              schema:
                type: string
                example: 120;w=60
        "400":
          $ref: "#/components/responses/ValidationError"
          description: Invalid request parameters
        "401":
          $ref: "#/components/responses/Unauthorized"
          description: Invalid or missing API key
      security:
        - BearerAuth: []
      parameters:
        - name: Idempotency-Key
          in: header
          required: false
          description: Optional client-supplied idempotency key. Retrying a request with the same key returns the original result instead of performing the action a second time; if omitted the server derives one from the canonical payload hash. Safe to retry network failures without duplicating side effects.
          schema:
            type: string
            minLength: 1
            maxLength: 255
  /routes/{id}:
    parameters:
      - name: id
        in: path
        required: true
        schema:
          type: string
          format: uuid
        description: Resource UUID
    patch:
      operationId: updateRoute
      summary: Update a recipient route
      tags:
        - Routes
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              additionalProperties: false
              properties:
                match_type:
                  type: string
                  enum:
                    - exact
                    - wildcard
                    - regex
                pattern:
                  type: string
                  minLength: 1
                  maxLength: 512
                endpoint_id:
                  type: string
                  format: uuid
                domain_id:
                  type:
                    - string
                    - "null"
                  format: uuid
                priority:
                  type: integer
                  minimum: 0
                  maximum: 1000000
                enabled:
                  type: boolean
      responses:
        "200":
          description: Updated route
          content:
            application/json:
              schema:
                allOf:
                  - type: object
                    properties:
                      success:
                        type: boolean
                        const: true
                    required:
                      - success
                      - data
                  - type: object
                    properties:
                      data:
                        type: object
                        description: A recipient routing rule binding an address pattern to one endpoint.
                        properties:
                          id:
                            type: string
                            format: uuid
                          org_id:
                            type: string
                            format: uuid
                          domain_id:
                            type:
                              - string
                              - "null"
                            format: uuid
                            description: Domain the route is scoped to; null = org-wide.
                          match_type:
                            type: string
                            enum:
                              - exact
                              - wildcard
                              - regex
                          pattern:
                            type: string
                            description: The recipient address pattern (an exact address or a wildcard).
                          pattern_norm:
                            type:
                              - string
                              - "null"
                            description: Normalized pattern used for matching.
                          endpoint_id:
                            type: string
                            format: uuid
                            description: The endpoint inbound mail matching this rule is delivered to.
                          priority:
                            type: integer
                            description: Evaluation order within a scope; lower is checked first.
                          enabled:
                            type: boolean
                          match_count:
                            type: string
                            description: How many emails have matched this rule (a bigint, returned as a string).
                          last_matched_at:
                            type:
                              - string
                              - "null"
                            format: date-time
                          created_at:
                            type: string
                            format: date-time
                        required:
                          - id
          headers:
            ratelimit-limit:
              description: Maximum number of requests allowed in the current window.
              schema:
                type: integer
                minimum: 1
                example: 120
            ratelimit-remaining:
              description: Remaining requests in the current window.
              schema:
                type: integer
                minimum: 0
                example: 118
            ratelimit-reset:
              description: Unix timestamp (seconds) when the current window resets.
              schema:
                type: integer
                example: 1700000060
            ratelimit-policy:
              description: Rate-limit policy in `limit;w=seconds` format, e.g. `120;w=60`.
              schema:
                type: string
                example: 120;w=60
        "400":
          $ref: "#/components/responses/ValidationError"
          description: Invalid request parameters
        "401":
          $ref: "#/components/responses/Unauthorized"
          description: Invalid or missing API key
        "403":
          $ref: "#/components/responses/Forbidden"
          description: Authenticated caller lacks permission for the operation
        "404":
          $ref: "#/components/responses/NotFound"
          description: Resource not found
        "409":
          $ref: "#/components/responses/Conflict"
          description: The request conflicts with the current state of the resource
      security:
        - BearerAuth: []
      description: Update a recipient route
    delete:
      operationId: deleteRoute
      summary: Delete a recipient route
      tags:
        - Routes
      responses:
        "200":
          description: Resource deleted
          content:
            application/json:
              schema:
                allOf:
                  - type: object
                    properties:
                      success:
                        type: boolean
                        const: true
                    required:
                      - success
                      - data
                  - type: object
                    properties:
                      data:
                        type: object
                        properties:
                          deleted:
                            type: boolean
                            const: true
                        required:
                          - deleted
          headers:
            ratelimit-limit:
              description: Maximum number of requests allowed in the current window.
              schema:
                type: integer
                minimum: 1
                example: 120
            ratelimit-remaining:
              description: Remaining requests in the current window.
              schema:
                type: integer
                minimum: 0
                example: 118
            ratelimit-reset:
              description: Unix timestamp (seconds) when the current window resets.
              schema:
                type: integer
                example: 1700000060
            ratelimit-policy:
              description: Rate-limit policy in `limit;w=seconds` format, e.g. `120;w=60`.
              schema:
                type: string
                example: 120;w=60
        "401":
          $ref: "#/components/responses/Unauthorized"
          description: Invalid or missing API key
        "404":
          $ref: "#/components/responses/NotFound"
          description: Resource not found
      security:
        - BearerAuth: []
      description: Delete a recipient route
  /webhooks/deliveries:
    get:
      operationId: listDeliveries
      summary: List webhook deliveries
      description: |
        Returns a paginated list of webhook delivery attempts. Each delivery
        includes a nested `email` object with sender, recipient, and subject.
      tags:
        - Webhook Deliveries
      parameters:
        - name: cursor
          in: query
          schema:
            type: string
          description: |
            Pagination cursor from a previous response's `meta.cursor` field.
            Format: `{ISO-datetime}|{id}`
        - name: limit
          in: query
          schema:
            type: integer
            minimum: 1
            maximum: 100
            default: 50
          description: Number of results per page
        - name: email_id
          in: query
          schema:
            type: string
            format: uuid
          description: Filter by email ID
        - name: status
          in: query
          schema:
            type: string
            enum:
              - pending
              - delivered
              - header_confirmed
              - failed
          description: Filter by delivery status
        - name: date_from
          in: query
          schema:
            type: string
            format: date-time
          description: Filter deliveries created on or after this timestamp
        - name: date_to
          in: query
          schema:
            type: string
            format: date-time
          description: Filter deliveries created on or before this timestamp
      responses:
        "200":
          description: Paginated list of deliveries
          content:
            application/json:
              schema:
                allOf:
                  - type: object
                    properties:
                      success:
                        type: boolean
                        const: true
                      meta:
                        type: object
                        properties:
                          total:
                            type: integer
                            description: Total number of matching records
                          limit:
                            type: integer
                            description: Page size used for this request
                          cursor:
                            type:
                              - string
                              - "null"
                            description: Cursor for the next page, or null if no more results
                        required:
                          - total
                          - limit
                          - cursor
                    required:
                      - success
                      - data
                      - meta
                  - type: object
                    properties:
                      data:
                        type: array
                        items:
                          type: object
                          properties:
                            id:
                              type: string
                              description: Delivery ID (numeric string)
                            email_id:
                              type: string
                              format: uuid
                            org_id:
                              type: string
                              format: uuid
                            endpoint_id:
                              type: string
                              format: uuid
                            endpoint_url:
                              type: string
                            status:
                              type: string
                              enum:
                                - pending
                                - delivered
                                - header_confirmed
                                - failed
                            attempt_count:
                              type: integer
                            duration_ms:
                              type:
                                - integer
                                - "null"
                            last_error:
                              type:
                                - string
                                - "null"
                            created_at:
                              type: string
                              format: date-time
                            updated_at:
                              type: string
                              format: date-time
                            email:
                              type:
                                - object
                                - "null"
                              properties:
                                sender:
                                  type: string
                                recipient:
                                  type: string
                                subject:
                                  type:
                                    - string
                                    - "null"
                              required:
                                - sender
                                - recipient
                          required:
                            - id
                            - email_id
                            - org_id
                            - endpoint_id
                            - endpoint_url
                            - status
                            - attempt_count
                            - created_at
                            - updated_at
          headers:
            ratelimit-limit:
              description: Maximum number of requests allowed in the current window.
              schema:
                type: integer
                minimum: 1
                example: 120
            ratelimit-remaining:
              description: Remaining requests in the current window.
              schema:
                type: integer
                minimum: 0
                example: 118
            ratelimit-reset:
              description: Unix timestamp (seconds) when the current window resets.
              schema:
                type: integer
                example: 1700000060
            ratelimit-policy:
              description: Rate-limit policy in `limit;w=seconds` format, e.g. `120;w=60`.
              schema:
                type: string
                example: 120;w=60
        "400":
          $ref: "#/components/responses/ValidationError"
          description: Invalid request parameters
        "401":
          $ref: "#/components/responses/Unauthorized"
          description: Invalid or missing API key
      security:
        - BearerAuth: []
  /webhooks/deliveries/{id}/replay:
    parameters:
      - name: id
        in: path
        required: true
        schema:
          type: string
          pattern: ^\d+$
        description: Delivery ID (numeric)
    post:
      operationId: replayDelivery
      summary: Replay a webhook delivery
      description: |
        Re-sends the stored webhook payload from a previous delivery attempt.
        If the original endpoint is still active, it is targeted. If the
        original endpoint was deleted, the oldest active endpoint is used.
        Deactivated endpoints cannot be replayed to. Rate limited per-org,
        sharing an org-wide budget with email replays.
      tags:
        - Webhook Deliveries
      responses:
        "200":
          description: Replay result
          content:
            application/json:
              schema:
                allOf:
                  - type: object
                    properties:
                      success:
                        type: boolean
                        const: true
                    required:
                      - success
                      - data
                  - type: object
                    properties:
                      data:
                        type: object
                        properties:
                          delivered:
                            type: integer
                            description: Number of successful deliveries
                          failed:
                            type: integer
                            description: Number of failed deliveries
                        required:
                          - delivered
                          - failed
          headers:
            ratelimit-limit:
              description: Maximum number of requests allowed in the current window.
              schema:
                type: integer
                minimum: 1
                example: 120
            ratelimit-remaining:
              description: Remaining requests in the current window.
              schema:
                type: integer
                minimum: 0
                example: 118
            ratelimit-reset:
              description: Unix timestamp (seconds) when the current window resets.
              schema:
                type: integer
                example: 1700000060
            ratelimit-policy:
              description: Rate-limit policy in `limit;w=seconds` format, e.g. `120;w=60`.
              schema:
                type: string
                example: 120;w=60
        "400":
          $ref: "#/components/responses/ValidationError"
          description: Invalid request parameters
        "401":
          $ref: "#/components/responses/Unauthorized"
          description: Invalid or missing API key
        "404":
          $ref: "#/components/responses/NotFound"
          description: Resource not found
        "429":
          $ref: "#/components/responses/RateLimited"
          description: Rate limit exceeded
      security:
        - BearerAuth: []
      parameters:
        - name: Idempotency-Key
          in: header
          required: false
          description: Optional client-supplied idempotency key. Retrying a request with the same key returns the original result instead of performing the action a second time; if omitted the server derives one from the canonical payload hash. Safe to retry network failures without duplicating side effects.
          schema:
            type: string
            minLength: 1
            maxLength: 255
  /send-permissions:
    get:
      operationId: getSendPermissions
      summary: List send-permission rules
      description: |
        Returns a flat list of rules describing every recipient the
        caller may send to. Each rule has a `type`, a kind-specific
        payload, and a human-readable `description`. If any rule
        matches the recipient, /send-mail will accept the send under
        the recipient-scope check.

        The endpoint is the answer to "where can I send" without
        exposing internal entitlement names. Agents that don't
        recognize a `type` can still read the `description` prose
        and act on it.

        Rule kinds, ordered broadest-first so an agent can stop
        scanning at the first match:

          1. `any_recipient` (one entry, only when the org can send
             anywhere): every other rule below it is redundant.
          2. `managed_zone` (always emitted, one per Primitive-managed
             zone): sends to any address at *.primitive.email or
             *.email.works always succeed; no entitlement required.
          3. `your_domain` (one per active verified outbound domain
             owned by the org): sends to that domain are approved.
          4. `address` (one per address that has authenticated
             inbound mail to the org, capped at `meta.address_cap`):
             sends to that exact address are approved.

        The list is informational, not an authorization check.
        /send-mail remains the source of truth on whether an
        individual send will succeed (it also enforces the
        from-address and the `send_mail` entitlement, which are
        not recipient-scope concerns and are not represented here).
      tags:
        - Sending
      responses:
        "200":
          description: Send-permission rules for the caller's org
          content:
            application/json:
              schema:
                allOf:
                  - type: object
                    properties:
                      success:
                        type: boolean
                        const: true
                    required:
                      - success
                      - data
                  - type: object
                    properties:
                      data:
                        type: array
                        items:
                          description: |
                            One recipient-scope rule describing a destination the caller
                            may send to. Discriminated on `type`. Each rule carries a
                            human-prose `description` field intended for display.

                            Rule kinds are stable within an SDK release. A response
                            containing a `type` value not enumerated in this schema
                            means the server is running a newer version than the SDK;
                            upgrade the SDK to the release that matches the server's
                            schema. Strict-parsing SDKs (Go, Python) will raise a
                            decode error in that case rather than silently dropping
                            the unknown rule, since silent drops would let an outbound
                            agent reason from an incomplete view of its own permissions.
                          discriminator:
                            propertyName: type
                            mapping:
                              any_recipient: "#/components/schemas/SendPermissionAnyRecipient"
                              managed_zone: "#/components/schemas/SendPermissionManagedZone"
                              your_domain: "#/components/schemas/SendPermissionYourDomain"
                              address: "#/components/schemas/SendPermissionAddress"
                          oneOf:
                            - type: object
                              description: |
                                The caller can send to any recipient. When this rule is
                                present, every other rule in the response is redundant.
                              properties:
                                type:
                                  type: string
                                  enum:
                                    - any_recipient
                                description:
                                  type: string
                                  description: Human-prose summary of the rule.
                              required:
                                - type
                                - description
                            - type: object
                              description: |
                                The caller can send to any address at the named
                                Primitive-managed zone. Always emitted (no entitlement
                                required) because Primitive owns the zone and every mailbox
                                belongs to a Primitive customer by construction.
                              properties:
                                type:
                                  type: string
                                  enum:
                                    - managed_zone
                                zone:
                                  type: string
                                  description: |
                                    The managed apex domain. Sends are accepted to any
                                    address at the apex itself or any subdomain (e.g.
                                    `alice@primitive.email` and `alice@acme.primitive.email`
                                    both match the `primitive.email` zone rule).
                                description:
                                  type: string
                                  description: Human-prose summary of the rule.
                              required:
                                - type
                                - zone
                                - description
                            - type: object
                              description: |
                                The caller can send to any address at one of their own
                                verified outbound domains. Emitted once per active row in
                                the org's `domains` table.
                              properties:
                                type:
                                  type: string
                                  enum:
                                    - your_domain
                                domain:
                                  type: string
                                  description: A verified outbound domain owned by the caller's org.
                                description:
                                  type: string
                                  description: Human-prose summary of the rule.
                              required:
                                - type
                                - domain
                                - description
                            - type: object
                              description: |
                                The caller can send to a specific address that has
                                authenticated inbound mail to the org. Emitted once per row
                                in the org's `known_send_addresses` table, capped at
                                `meta.address_cap`.
                              properties:
                                type:
                                  type: string
                                  enum:
                                    - address
                                address:
                                  type: string
                                  description: The bare email address this rule grants sends to.
                                last_received_at:
                                  type: string
                                  format: date-time
                                  description: |
                                    Most recent inbound email from this address that
                                    authenticated successfully (DMARC pass + DKIM/SPF
                                    alignment). Updated on each new authenticated receipt.
                                received_count:
                                  type: integer
                                  description: |
                                    Total number of authenticated inbound emails from this
                                    address. Increments only when `last_received_at` advances.
                                description:
                                  type: string
                                  description: Human-prose summary of the rule.
                              required:
                                - type
                                - address
                                - last_received_at
                                - received_count
                                - description
                      meta:
                        type: object
                        description: |
                          Response metadata for /send-permissions. The `address_cap`
                          bounds the size of the `address` rule subset; orgs with more
                          than `address_cap` known addresses almost always also hold a
                          broader rule type (`any_recipient` or `your_domain`), so the
                          cap is a response-size bound rather than a meaningful
                          product limit.
                        properties:
                          address_cap:
                            type: integer
                            description: Maximum number of `address` rules included in `data`.
                          truncated:
                            type: boolean
                            description: |
                              True when the org has more than `address_cap` known
                              addresses and the list was truncated. False when every
                              known address is represented or when the org holds no
                              address rules at all.
                        required:
                          - address_cap
                          - truncated
                    required:
                      - data
                      - meta
          headers:
            ratelimit-limit:
              description: Maximum number of requests allowed in the current window.
              schema:
                type: integer
                minimum: 1
                example: 120
            ratelimit-remaining:
              description: Remaining requests in the current window.
              schema:
                type: integer
                minimum: 0
                example: 118
            ratelimit-reset:
              description: Unix timestamp (seconds) when the current window resets.
              schema:
                type: integer
                example: 1700000060
            ratelimit-policy:
              description: Rate-limit policy in `limit;w=seconds` format, e.g. `120;w=60`.
              schema:
                type: string
                example: 120;w=60
        "401":
          $ref: "#/components/responses/Unauthorized"
          description: Invalid or missing API key
      security:
        - BearerAuth: []
  /send-mail:
    post:
      operationId: sendEmail
      summary: Send outbound email
      description: |
        Sends an outbound email through Primitive's outbound relay. By default
        the request returns once the relay accepts the message for delivery.
        Set `wait: true` to wait for the first downstream SMTP delivery outcome.

        **Host routing.** /send-mail is served by the canonical API host
        (`https://api.primitive.dev/v1`) so the request body can carry
        inline attachments up to ~30 MiB raw. The legacy dashboard
        compatibility host (`https://www.primitive.dev/api/v1`) also accepts
        /send-mail, but Vercel request body limits apply before proxying.
        The typed SDKs route /send-mail to the canonical API host
        automatically.
      servers:
        - url: https://api.primitive.dev/v1
          description: Canonical API host (recommended)
        - url: https://www.primitive.dev/api/v1
          description: Legacy compatibility host (Vercel body limit applies)
      tags:
        - Sending
      parameters:
        - name: Idempotency-Key
          in: header
          required: false
          schema:
            type: string
            minLength: 1
            maxLength: 255
            pattern: ^[\x21-\x7E]+$
          description: |
            Optional customer-supplied idempotency key. If omitted, Primitive
            derives one from the canonical request payload and echoes the
            effective value in the `Idempotency-Key` response header.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              additionalProperties: false
              properties:
                from:
                  type: string
                  minLength: 3
                  maxLength: 998
                  description: RFC 5322 From header. The sender domain must be a verified outbound domain for your organization.
                to:
                  type: string
                  minLength: 3
                  maxLength: 320
                  description: Recipient address. Recipient eligibility depends on your account's outbound entitlements.
                subject:
                  type: string
                  minLength: 1
                  maxLength: 998
                  description: Subject line for the outbound message
                body_text:
                  type: string
                  description: Plain-text message body. At least one of body_text or body_html is required. The combined UTF-8 byte length of body_text and body_html must be at most 262144 bytes.
                body_html:
                  type: string
                  description: HTML message body. At least one of body_text or body_html is required. The combined UTF-8 byte length of body_text and body_html must be at most 262144 bytes.
                in_reply_to:
                  type: string
                  minLength: 1
                  maxLength: 998
                  pattern: ^[^\x00-\x1F\x7F]+$
                  description: Message-ID of the direct parent email when sending a threaded reply.
                references:
                  type: array
                  maxItems: 100
                  description: Full ordered message-id chain for the thread.
                  items:
                    type: string
                    minLength: 1
                    maxLength: 998
                    pattern: ^[^\x00-\x1F\x7F]+$
                attachments:
                  type: array
                  maxItems: 100
                  description: Inline attachments. Send requests with attachments to https://api.primitive.dev/v1/send-mail. Combined raw decoded attachment bytes must be at most 31457280.
                  items:
                    type: object
                    additionalProperties: false
                    properties:
                      filename:
                        type: string
                        minLength: 1
                        maxLength: 255
                        description: Attachment filename. Control characters are rejected.
                      content_type:
                        type: string
                        minLength: 1
                        maxLength: 255
                        description: Optional MIME content type. Control characters are rejected.
                      content_base64:
                        type: string
                        minLength: 1
                        maxLength: 44040192
                        description: Base64-encoded attachment bytes.
                    required:
                      - filename
                      - content_base64
                wait:
                  type: boolean
                  description: When true, wait for the first downstream SMTP delivery outcome before returning.
                wait_timeout_ms:
                  type: integer
                  minimum: 1000
                  maximum: 30000
                  description: Maximum time to wait for a delivery outcome when wait is true. Defaults to 30000.
              required:
                - from
                - to
                - subject
      responses:
        "200":
          description: Outbound relay result
          content:
            application/json:
              schema:
                allOf:
                  - type: object
                    properties:
                      success:
                        type: boolean
                        const: true
                    required:
                      - success
                      - data
                  - type: object
                    properties:
                      data:
                        type: object
                        properties:
                          id:
                            type: string
                            description: Persisted sent-email attempt ID.
                          status:
                            type: string
                            description: |
                              Lifecycle status of a sent_emails row. Possible values:

                                - `queued`: pre-call INSERT; the outbound agent has not
                                  yet replied.
                                - `submitted_to_agent`: agent accepted; `queue_id` is set.
                                - `agent_failed`: agent rejected; `error_code` and
                                  `error_message` carry the reason.
                                - `gate_denied`: a recipient-scope gate denied the send;
                                  the agent was never called. The `gates` array carries
                                  the denial detail. /send-mail returns 403 in this case
                                  so callers see the denial synchronously; /sent-emails
                                  additionally records the row for historical lookup,
                                  which is when this status appears in a listing.
                                - `unknown`: terminal indeterminate; the on-box log
                                  poller couldn't classify the receiver's response.
                                - `delivered` / `bounced` / `deferred` / `wait_timeout`:
                                  terminal delivery outcomes (see DeliveryStatus).
                            enum:
                              - queued
                              - submitted_to_agent
                              - agent_failed
                              - gate_denied
                              - unknown
                              - delivered
                              - bounced
                              - deferred
                              - wait_timeout
                          from:
                            type: string
                            description: |
                              Bare from-address actually written on the wire. Echoed
                              on every success branch so callers can confirm what
                              went out, particularly useful for the /emails/{id}/reply
                              path where `from` is server-derived from the inbound's
                              recipient when the caller doesn't override.

                              For sends where the caller passed a from-header that
                              included a display name (e.g. `"Acme Support" <support@acme.test>`),
                              this field is the parsed bare address (`support@acme.test`).
                              The display name was sent on the wire intact; this field
                              just makes the address easy to compare against allowlists.
                          queue_id:
                            type:
                              - string
                              - "null"
                            description: |
                              Message identifier assigned by Primitive's OUTBOUND relay
                              (the box that signs your mail and submits it to the
                              receiving MTA). NOT the receiver's queue id.

                              The receiver may also report its own queue id in
                              `smtp_response_text` (e.g. `"250 2.0.0 Ok: queued as
                              99D111927CDA"` from a Postfix receiver). Those two ids
                              refer to different mail systems and are NOT comparable.
                              Treat `queue_id` as Primitive-internal and the
                              receiver's id as remote-system-internal.

                              Null on rows that never reached the relay (queued,
                              gate_denied, agent_failed before signing).
                          accepted:
                            type: array
                            items:
                              type: string
                            description: Recipient addresses accepted by the relay.
                          rejected:
                            type: array
                            items:
                              type: string
                            description: Recipient addresses rejected by the relay.
                          client_idempotency_key:
                            type: string
                            description: Effective idempotency key used for this send.
                          request_id:
                            type: string
                            description: Server-issued request identifier for support and tracing.
                          content_hash:
                            type: string
                            description: Stable hash of the canonical send payload.
                          delivery_status:
                            type: string
                            description: |
                              Narrower enum covering only the four terminal delivery
                              outcomes returned to a synchronous `wait: true` send.

                              On the SendMailResult shape, `delivery_status` is always
                              equal to `status` whenever both are present (i.e. on
                              terminal-state replays and live wait=true responses).
                              The two fields exist so callers that want to type-narrow
                              on "this is a delivery outcome" can pattern-match against
                              the four-value enum without handling the broader
                              SentEmailStatus value set (which also covers `queued`,
                              `submitted_to_agent`, `agent_failed`, `gate_denied`,
                              `unknown`).

                              On async-mode and pre-terminal responses, `delivery_status`
                              is absent and only `status` is populated. Use `status` if
                              you want a single field that's always present.
                            enum:
                              - delivered
                              - bounced
                              - deferred
                              - wait_timeout
                          smtp_response_code:
                            type:
                              - integer
                              - "null"
                            description: SMTP response code from the first downstream delivery outcome when wait is true.
                          smtp_response_text:
                            type: string
                            description: SMTP response text from the first downstream delivery outcome when wait is true.
                          idempotent_replay:
                            type: boolean
                            description: |
                              True when the response replays a previously-recorded send
                              keyed by `client_idempotency_key` (same key, same canonical
                              payload). False on a fresh send and on gate-denied
                              responses. Lets callers branch on cache state without
                              diffing fields.
                        required:
                          - id
                          - status
                          - from
                          - queue_id
                          - accepted
                          - rejected
                          - client_idempotency_key
                          - request_id
                          - content_hash
                          - idempotent_replay
          headers:
            ratelimit-limit:
              description: Maximum number of requests allowed in the current window.
              schema:
                type: integer
                minimum: 1
                example: 120
            ratelimit-remaining:
              description: Remaining requests in the current window.
              schema:
                type: integer
                minimum: 0
                example: 118
            ratelimit-reset:
              description: Unix timestamp (seconds) when the current window resets.
              schema:
                type: integer
                example: 1700000060
            ratelimit-policy:
              description: Rate-limit policy in `limit;w=seconds` format, e.g. `120;w=60`.
              schema:
                type: string
                example: 120;w=60
            Idempotency-Key:
              description: Effective idempotency key for this request. When the caller supplies an `Idempotency-Key` request header the same value is echoed; otherwise the server derives one from the canonical payload hash. Use this to correlate a request to its stored sent-email record via the `idempotency_key` filter on `GET /sent-emails`.
              schema:
                type: string
                minLength: 1
                maxLength: 255
        "400":
          $ref: "#/components/responses/ValidationError"
          description: Invalid request parameters
        "401":
          $ref: "#/components/responses/Unauthorized"
          description: Invalid or missing API key
        "403":
          $ref: "#/components/responses/Forbidden"
          description: Authenticated caller lacks permission for the operation
        "429":
          $ref: "#/components/responses/RateLimited"
          description: Rate limit exceeded
        "500":
          $ref: "#/components/responses/InternalError"
          description: Primitive encountered an internal error
        "502":
          $ref: "#/components/responses/BadGateway"
          description: Primitive could not complete the downstream SMTP request
        "503":
          $ref: "#/components/responses/ServiceUnavailable"
          description: Primitive is temporarily unable to process the request
      security:
        - BearerAuth: []
  /semantic-search:
    post:
      operationId: semanticSearch
      summary: Semantic search across received and sent mail
      description: |
        Ranked search across both received and sent mail. The `mode`
        field selects the ranking strategy:

        - `keyword`: lexical full-text matching only (no embeddings).
        - `semantic`: meaning-based matching using vector embeddings.
        - `hybrid` (default): blends the semantic and keyword signals.

        Results are ordered by a relevance `score`. Every row reports the
        fields it matched (`matched_fields`), a match-centered excerpt per
        field (`snippets`), and a `score_breakdown` whose components account
        for the `score`. Page through results by passing the prior
        response's `meta.cursor` back as `cursor`.

        Requires the Pro plan and the `semantic_search_enabled`
        entitlement; callers without them receive `403`.

        Host routing: this operation is served only by the search host
        (`https://api.primitive.dev/v1`). The typed SDKs route it there
        automatically.
      servers:
        - url: https://api.primitive.dev/v1
          description: Search host
      tags:
        - Search
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                query:
                  type: string
                  minLength: 1
                  maxLength: 2048
                  description: |
                    Free-text query. Required for `semantic` and `hybrid` modes;
                    optional for `keyword` mode.
                mode:
                  type: string
                  enum:
                    - hybrid
                    - semantic
                    - keyword
                  default: hybrid
                  description: |
                    Ranking strategy. `keyword` is lexical only, `semantic` is
                    embedding-based, `hybrid` blends both.
                corpus:
                  type: array
                  items:
                    type: string
                    enum:
                      - inbound
                      - outbound
                  minItems: 1
                  maxItems: 2
                  description: |
                    Which mail to search. Defaults to both received (`inbound`)
                    and sent (`outbound`).
                search_in:
                  type: array
                  items:
                    type: string
                    enum:
                      - subject
                      - headers
                      - addresses
                      - body
                    description: A searchable email field.
                  description: Restrict matching to these fields. Defaults to all.
                exclude:
                  type: array
                  items:
                    type: string
                    enum:
                      - subject
                      - headers
                      - addresses
                      - body
                    description: A searchable email field.
                  description: Exclude these fields from matching.
                date_from:
                  type: string
                  format: date-time
                  description: Only include mail at or after this timestamp.
                date_to:
                  type: string
                  format: date-time
                  description: Only include mail at or before this timestamp.
                include:
                  type: array
                  items:
                    type: string
                    enum:
                      - coverage
                  description: |
                    Opt-in extras. `coverage` adds an index-coverage snapshot to
                    `meta`. Matched fields, snippets, and the score breakdown are
                    always returned regardless of this field.
                limit:
                  type: integer
                  minimum: 1
                  maximum: 100
                  default: 10
                  description: Maximum number of results to return.
                cursor:
                  type: string
                  description: Opaque pagination cursor from a prior response's `meta.cursor`.
      responses:
        "200":
          description: Ranked search results
          content:
            application/json:
              schema:
                allOf:
                  - type: object
                    properties:
                      success:
                        type: boolean
                        const: true
                    required:
                      - success
                      - data
                  - type: object
                    properties:
                      data:
                        type: array
                        items:
                          type: object
                          properties:
                            source_type:
                              type: string
                              enum:
                                - inbound_email
                                - sent_email
                              description: Whether this row is a received or sent message.
                            id:
                              type: string
                              description: Message id. Combine with `api_url` to fetch the full record.
                            subject:
                              type:
                                - string
                                - "null"
                            from:
                              type:
                                - string
                                - "null"
                            to:
                              type:
                                - string
                                - "null"
                            timestamp:
                              type: string
                              description: Message timestamp (received_at for inbound, created_at for sent).
                            status:
                              type: string
                              description: Lifecycle status of the message.
                            score:
                              type: number
                              description: Overall relevance score; the `score_breakdown` components account for it.
                            semantic_score:
                              type:
                                - number
                                - "null"
                              description: Raw semantic similarity signal, or null when not applicable.
                            keyword_score:
                              type:
                                - number
                                - "null"
                              description: Raw keyword (lexical) signal, or null when not applicable.
                            matched_fields:
                              type: array
                              items:
                                type: string
                                enum:
                                  - subject
                                  - headers
                                  - addresses
                                  - body
                                description: A searchable email field.
                              description: Fields where the query matched.
                            snippets:
                              type: array
                              items:
                                type: object
                                properties:
                                  field:
                                    type: string
                                    description: The field this excerpt came from.
                                  text:
                                    type: string
                                    description: Plain-text excerpt centered on the match (no markup).
                                required:
                                  - field
                                  - text
                              description: Match-centered excerpts, one per matched field.
                            score_breakdown:
                              type: object
                              description: |
                                Additive contributions to `score`. `semantic` and `keyword` are the
                                raw signals times the mode's weight (null when not applicable);
                                these plus `field_boost` and `recency` sum to `score` before each
                                value is independently rounded to 5 decimal places.
                              properties:
                                semantic:
                                  type:
                                    - number
                                    - "null"
                                keyword:
                                  type:
                                    - number
                                    - "null"
                                field_boost:
                                  type: number
                                recency:
                                  type: number
                              required:
                                - semantic
                                - keyword
                                - field_boost
                                - recency
                            api_url:
                              type:
                                - string
                                - "null"
                              description: Relative API path to fetch the full message.
                          required:
                            - source_type
                            - id
                            - subject
                            - from
                            - to
                            - timestamp
                            - status
                            - score
                            - semantic_score
                            - keyword_score
                            - matched_fields
                            - snippets
                            - score_breakdown
                            - api_url
                      meta:
                        type: object
                        properties:
                          limit:
                            type: integer
                            description: Page size used for this request.
                          cursor:
                            type:
                              - string
                              - "null"
                            description: Cursor for the next page, or null if there are no more results.
                          mode:
                            type: string
                            enum:
                              - hybrid
                              - semantic
                              - keyword
                            description: Ranking mode used for this response.
                          coverage:
                            oneOf:
                              - type: object
                                description: Index-coverage snapshot for the org, returned only when the `coverage` include option is requested.
                                properties:
                                  embedded_chunks:
                                    type: integer
                                  pending_chunks:
                                    type: integer
                                  skipped_plan_chunks:
                                    type: integer
                                  skipped_quota_chunks:
                                    type: integer
                                  unsupported_attachment_chunks:
                                    type: integer
                                  failed_chunks:
                                    type: integer
                                required:
                                  - embedded_chunks
                                  - pending_chunks
                                  - skipped_plan_chunks
                                  - skipped_quota_chunks
                                  - unsupported_attachment_chunks
                                  - failed_chunks
                              - type: "null"
                            description: |
                              Index-coverage snapshot, present only when requested via
                              `include: [coverage]`; otherwise null.
                        required:
                          - limit
                          - cursor
                          - mode
                          - coverage
                    required:
                      - data
                      - meta
          headers:
            ratelimit-limit:
              description: Maximum number of requests allowed in the current window.
              schema:
                type: integer
                minimum: 1
                example: 120
            ratelimit-remaining:
              description: Remaining requests in the current window.
              schema:
                type: integer
                minimum: 0
                example: 118
            ratelimit-reset:
              description: Unix timestamp (seconds) when the current window resets.
              schema:
                type: integer
                example: 1700000060
            ratelimit-policy:
              description: Rate-limit policy in `limit;w=seconds` format, e.g. `120;w=60`.
              schema:
                type: string
                example: 120;w=60
        "400":
          $ref: "#/components/responses/ValidationError"
          description: Invalid request parameters
        "401":
          $ref: "#/components/responses/Unauthorized"
          description: Invalid or missing API key
        "403":
          $ref: "#/components/responses/Forbidden"
          description: Authenticated caller lacks permission for the operation
        "429":
          $ref: "#/components/responses/RateLimited"
          description: Rate limit exceeded
        "500":
          $ref: "#/components/responses/InternalError"
          description: Primitive encountered an internal error
        "503":
          $ref: "#/components/responses/ServiceUnavailable"
          description: Primitive is temporarily unable to process the request
      security:
        - BearerAuth: []
      parameters:
        - name: Idempotency-Key
          in: header
          required: false
          description: Optional client-supplied idempotency key. Retrying a request with the same key returns the original result instead of performing the action a second time; if omitted the server derives one from the canonical payload hash. Safe to retry network failures without duplicating side effects.
          schema:
            type: string
            minLength: 1
            maxLength: 255
  /sent-emails:
    get:
      operationId: listSentEmails
      summary: List outbound sent emails
      description: |
        Returns a paginated list of OUTBOUND emails the caller's
        org has sent via /send-mail (and /emails/{id}/reply, which
        forwards through /send-mail). Includes every recorded
        attempt, including gate-denied attempts that the agent
        never called and rows still in `queued` state.

        For inbound mail received at your verified domains, see
        /emails. There is no unified send/receive history endpoint;
        the two surfaces are intentionally separate because the
        underlying tables, statuses, and lifecycle differ.

        Email bodies (`body_text`, `body_html`) are NOT included on
        list rows so a 50-row page can't balloon into a multi-MB
        response when sends are near the 5MB body cap. Use
        /sent-emails/{id} to fetch a single row with bodies, or
        cross-reference by `client_idempotency_key` if the caller
        already has the body locally.
      tags:
        - Sending
      parameters:
        - name: cursor
          in: query
          schema:
            type: string
          description: |
            Pagination cursor from a previous response's `meta.cursor` field.
            Format: `{ISO-datetime}|{id}`
        - name: limit
          in: query
          schema:
            type: integer
            minimum: 1
            maximum: 100
            default: 50
          description: Number of results per page
        - name: status
          in: query
          schema:
            type: string
            description: |
              Lifecycle status of a sent_emails row. Possible values:

                - `queued`: pre-call INSERT; the outbound agent has not
                  yet replied.
                - `submitted_to_agent`: agent accepted; `queue_id` is set.
                - `agent_failed`: agent rejected; `error_code` and
                  `error_message` carry the reason.
                - `gate_denied`: a recipient-scope gate denied the send;
                  the agent was never called. The `gates` array carries
                  the denial detail. /send-mail returns 403 in this case
                  so callers see the denial synchronously; /sent-emails
                  additionally records the row for historical lookup,
                  which is when this status appears in a listing.
                - `unknown`: terminal indeterminate; the on-box log
                  poller couldn't classify the receiver's response.
                - `delivered` / `bounced` / `deferred` / `wait_timeout`:
                  terminal delivery outcomes (see DeliveryStatus).
            enum:
              - queued
              - submitted_to_agent
              - agent_failed
              - gate_denied
              - unknown
              - delivered
              - bounced
              - deferred
              - wait_timeout
          description: |
            Filter to rows in this status. Useful for polling
            queued rows that haven't transitioned, auditing
            gate-denied attempts, or listing only successful
            deliveries.
        - name: request_id
          in: query
          schema:
            type: string
            format: uuid
          description: |
            Filter to the row matching a specific server-issued
            `request_id`. The /send-mail response surfaces
            `request_id` on every send; this lookup lets the
            caller find the historical row for a given live call
            without remembering its `id`.
        - name: idempotency_key
          in: query
          schema:
            type: string
            minLength: 1
            maxLength: 255
          description: |
            Filter to rows with the given `client_idempotency_key`.
            Multiple rows can share a key (a retry that hit the
            idempotent-replay path returns the same row, but a
            retry with a DIFFERENT canonical payload under the
            same key is rejected by /send-mail before the row is
            written, so duplicates are bounded).
        - name: date_from
          in: query
          schema:
            type: string
            format: date-time
          description: Inclusive lower bound on `created_at`.
        - name: date_to
          in: query
          schema:
            type: string
            format: date-time
          description: Inclusive upper bound on `created_at`.
      responses:
        "200":
          description: Page of sent-email summaries
          content:
            application/json:
              schema:
                allOf:
                  - type: object
                    properties:
                      success:
                        type: boolean
                        const: true
                      meta:
                        type: object
                        properties:
                          total:
                            type: integer
                            description: Total number of matching records
                          limit:
                            type: integer
                            description: Page size used for this request
                          cursor:
                            type:
                              - string
                              - "null"
                            description: Cursor for the next page, or null if no more results
                        required:
                          - total
                          - limit
                          - cursor
                    required:
                      - success
                      - data
                      - meta
                  - type: object
                    properties:
                      data:
                        type: array
                        items:
                          type: object
                          description: |
                            List-row projection of a sent-email record. Drops
                            `body_text` and `body_html` to keep paginated responses
                            small; fetch /sent-emails/{id} for the full record with
                            bodies.
                          properties:
                            id:
                              type: string
                              format: uuid
                            status:
                              type: string
                              description: |
                                Lifecycle status of a sent_emails row. Possible values:

                                  - `queued`: pre-call INSERT; the outbound agent has not
                                    yet replied.
                                  - `submitted_to_agent`: agent accepted; `queue_id` is set.
                                  - `agent_failed`: agent rejected; `error_code` and
                                    `error_message` carry the reason.
                                  - `gate_denied`: a recipient-scope gate denied the send;
                                    the agent was never called. The `gates` array carries
                                    the denial detail. /send-mail returns 403 in this case
                                    so callers see the denial synchronously; /sent-emails
                                    additionally records the row for historical lookup,
                                    which is when this status appears in a listing.
                                  - `unknown`: terminal indeterminate; the on-box log
                                    poller couldn't classify the receiver's response.
                                  - `delivered` / `bounced` / `deferred` / `wait_timeout`:
                                    terminal delivery outcomes (see DeliveryStatus).
                              enum:
                                - queued
                                - submitted_to_agent
                                - agent_failed
                                - gate_denied
                                - unknown
                                - delivered
                                - bounced
                                - deferred
                                - wait_timeout
                            status_changed_at:
                              type: string
                              format: date-time
                              description: |
                                Timestamp of the most recent status transition.
                                Polling clients should treat `status='queued'` AND
                                `status_changed_at` older than 5 minutes as
                                "stuck-queued" (the post-tx UPDATE failed and the
                                actual delivery state is recoverable from on-box logs
                                via `queue_id` when populated, or `request_id`).
                            created_at:
                              type: string
                              format: date-time
                            updated_at:
                              type: string
                              format: date-time
                            client_idempotency_key:
                              type:
                                - string
                                - "null"
                              description: |
                                Effective idempotency key used for this send. If the
                                caller passed the `Idempotency-Key` header, this is
                                that value; otherwise it's a server-derived hash of
                                the canonical request payload.
                            content_hash:
                              type: string
                              description: Stable hash of the canonical send payload.
                            from_header:
                              type: string
                              description: |
                                Raw `From:` header as sent on the wire, including any
                                display name (e.g. `"Acme Support" <agent@acme.test>`).
                            from_address:
                              type: string
                              description: Bare email address parsed from `from_header`.
                            to_header:
                              type: string
                              description: |
                                Raw `To:` header as sent on the wire, including any
                                display name.
                            to_address:
                              type: string
                              description: Bare email address parsed from `to_header`.
                            subject:
                              type: string
                            body_size_bytes:
                              type: integer
                              description: |
                                Total UTF-8 byte length of `body_text` + `body_html`.
                                Surfaced on the list endpoint so callers can see "this
                                row has a 4MB body" without fetching it.
                            content_discarded_at:
                              type:
                                - string
                                - "null"
                              format: date-time
                              description: |
                                Timestamp at which the bodies were discarded by an
                                entitlement-driven retention policy. Null when bodies
                                are still present. The detail endpoint returns
                                null-valued `body_text`/`body_html` for discarded rows.
                            message_id:
                              type:
                                - string
                                - "null"
                              description: |
                                Wire-level Message-ID assigned to the outbound message
                                (RFC 5322). Null on rows that never reached signing
                                (queued, gate_denied, agent_failed before signing).
                            in_reply_to:
                              type:
                                - string
                                - "null"
                              description: |
                                Wire-level In-Reply-To header value, when this send
                                was a reply.
                            email_references:
                              type:
                                - string
                                - "null"
                              description: |
                                Wire-level References header value, when this send
                                was a reply.
                            in_reply_to_email_id:
                              type:
                                - string
                                - "null"
                              format: uuid
                              description: |
                                Reference to the inbound `emails.id` that this send
                                replied to, when known. Populated when the caller used
                                /emails/{id}/reply or when /send-mail's `in_reply_to`
                                matched a stored inbound message_id in the same org.
                            thread_id:
                              type:
                                - string
                                - "null"
                              format: uuid
                              description: |
                                Conversation thread this send belongs to. A reply inherits
                                the thread of the inbound it answers; a fresh send starts a
                                new thread. Fetch `/threads/{thread_id}` for the full
                                ordered thread (inbound + outbound interleaved). NULL on
                                gate-denied sends and on sends created before threading was
                                enabled.
                            queue_id:
                              type:
                                - string
                                - "null"
                              description: |
                                Message identifier assigned by Primitive's outbound
                                relay once the agent accepts the message. Null on
                                queued, gate_denied, and agent_failed rows.
                            smtp_response_code:
                              type:
                                - integer
                                - "null"
                              description: |
                                Receiver's 3-digit SMTP code (e.g. 250, 550, 451).
                                Populated on terminal delivery statuses; may be null
                                on a deferred where the agent never got an SMTP-level
                                response (TCP refused, DNS failed, TLS handshake
                                failed). `smtp_response_text` still carries Postfix's
                                descriptive text in those cases.
                            smtp_response_text:
                              type:
                                - string
                                - "null"
                              description: |
                                Free-form text portion of the receiver's SMTP
                                response. The most useful debugging signal on a
                                `bounced` or `deferred` row.
                            smtp_enhanced_status_code:
                              type:
                                - string
                                - "null"
                              description: |
                                RFC 3463 enhanced status code (e.g. `5.1.1` for "Bad
                                destination mailbox address"). Distinct from
                                `smtp_response_code`: the basic 3-digit code is coarse
                                (550 = "permanent failure"), the enhanced code is
                                finer-grained.
                            dkim_selector:
                              type:
                                - string
                                - "null"
                              description: |
                                DKIM selector used to sign the outbound message.
                                Public DNS data; useful for diagnosing why a downstream
                                verifier rejected the signature.
                            dkim_domain:
                              type:
                                - string
                                - "null"
                              description: DKIM signing domain.
                            error_code:
                              type:
                                - string
                                - "null"
                              description: |
                                Stable public error code on `agent_failed` rows. The
                                agent's internal codes are remapped to a stable public
                                taxonomy (see `publicAgentError` in the server) so this
                                field is safe to branch on across agent versions.
                            error_message:
                              type:
                                - string
                                - "null"
                              description: Free-form error message accompanying `error_code`.
                            gates:
                              type:
                                - array
                                - "null"
                              items:
                                type: object
                                properties:
                                  name:
                                    type: string
                                    enum:
                                      - send_to_confirmed_domains
                                      - send_to_known_addresses
                                    description: Public recipient-scope gate name that denied the send.
                                  reason:
                                    type: string
                                    enum:
                                      - domain_not_confirmed
                                      - recipient_unauthenticated
                                      - recipient_not_known
                                    description: Stable machine-readable denial reason.
                                  message:
                                    type: string
                                    description: Human-readable explanation of the gate denial.
                                  subject:
                                    type: string
                                    description: Domain or address the gate evaluated.
                                  fix:
                                    type: object
                                    properties:
                                      action:
                                        type: string
                                        enum:
                                          - confirm_domain
                                          - sender_must_fix_authentication
                                          - wait_for_inbound
                                        description: Suggested next action for the caller.
                                      subject:
                                        type: string
                                        description: Entity the action applies to.
                                    required:
                                      - action
                                      - subject
                                  docs_url:
                                    type: string
                                    description: Public docs URL with more context.
                                required:
                                  - name
                                  - reason
                                  - message
                                  - subject
                              description: |
                                Gate-denial detail on `gate_denied` rows. Mirrors the
                                synchronous /send-mail 403 contract so a caller's
                                GateDenial handler is the same across live denies and
                                historical lookups. Null on every other status.
                            request_id:
                              type:
                                - string
                                - "null"
                              description: |
                                Server-issued request identifier from the original
                                /send-mail call. Surfaced as the `X-Request-Id`
                                response header on the live send and recorded here
                                for support escalation.
                          required:
                            - id
                            - status
                            - status_changed_at
                            - created_at
                            - updated_at
                            - content_hash
                            - from_header
                            - from_address
                            - to_header
                            - to_address
                            - subject
                            - body_size_bytes
          headers:
            ratelimit-limit:
              description: Maximum number of requests allowed in the current window.
              schema:
                type: integer
                minimum: 1
                example: 120
            ratelimit-remaining:
              description: Remaining requests in the current window.
              schema:
                type: integer
                minimum: 0
                example: 118
            ratelimit-reset:
              description: Unix timestamp (seconds) when the current window resets.
              schema:
                type: integer
                example: 1700000060
            ratelimit-policy:
              description: Rate-limit policy in `limit;w=seconds` format, e.g. `120;w=60`.
              schema:
                type: string
                example: 120;w=60
        "400":
          $ref: "#/components/responses/ValidationError"
          description: Invalid request parameters
        "401":
          $ref: "#/components/responses/Unauthorized"
          description: Invalid or missing API key
      security:
        - BearerAuth: []
  /sent-emails/{id}:
    get:
      operationId: getSentEmail
      summary: Get a sent email by id
      description: |
        Returns the full sent-email record by id, including
        `body_text` and `body_html` (omitted from the listing
        endpoint to keep paginated responses small). Use this when
        diagnosing a specific send, e.g. inspecting the receiver's
        SMTP response on a `bounced` row or pulling the gate
        denial detail on a `gate_denied` row.
      tags:
        - Sending
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
            format: uuid
          description: Resource UUID
      responses:
        "200":
          description: Sent-email detail
          content:
            application/json:
              schema:
                allOf:
                  - type: object
                    properties:
                      success:
                        type: boolean
                        const: true
                    required:
                      - success
                      - data
                  - type: object
                    properties:
                      data:
                        description: |
                          Full sent-email record, including `body_text` and
                          `body_html`. Returned by /sent-emails/{id}.
                        allOf:
                          - type: object
                            description: |
                              List-row projection of a sent-email record. Drops
                              `body_text` and `body_html` to keep paginated responses
                              small; fetch /sent-emails/{id} for the full record with
                              bodies.
                            properties:
                              id:
                                type: string
                                format: uuid
                              status:
                                type: string
                                description: |
                                  Lifecycle status of a sent_emails row. Possible values:

                                    - `queued`: pre-call INSERT; the outbound agent has not
                                      yet replied.
                                    - `submitted_to_agent`: agent accepted; `queue_id` is set.
                                    - `agent_failed`: agent rejected; `error_code` and
                                      `error_message` carry the reason.
                                    - `gate_denied`: a recipient-scope gate denied the send;
                                      the agent was never called. The `gates` array carries
                                      the denial detail. /send-mail returns 403 in this case
                                      so callers see the denial synchronously; /sent-emails
                                      additionally records the row for historical lookup,
                                      which is when this status appears in a listing.
                                    - `unknown`: terminal indeterminate; the on-box log
                                      poller couldn't classify the receiver's response.
                                    - `delivered` / `bounced` / `deferred` / `wait_timeout`:
                                      terminal delivery outcomes (see DeliveryStatus).
                                enum:
                                  - queued
                                  - submitted_to_agent
                                  - agent_failed
                                  - gate_denied
                                  - unknown
                                  - delivered
                                  - bounced
                                  - deferred
                                  - wait_timeout
                              status_changed_at:
                                type: string
                                format: date-time
                                description: |
                                  Timestamp of the most recent status transition.
                                  Polling clients should treat `status='queued'` AND
                                  `status_changed_at` older than 5 minutes as
                                  "stuck-queued" (the post-tx UPDATE failed and the
                                  actual delivery state is recoverable from on-box logs
                                  via `queue_id` when populated, or `request_id`).
                              created_at:
                                type: string
                                format: date-time
                              updated_at:
                                type: string
                                format: date-time
                              client_idempotency_key:
                                type:
                                  - string
                                  - "null"
                                description: |
                                  Effective idempotency key used for this send. If the
                                  caller passed the `Idempotency-Key` header, this is
                                  that value; otherwise it's a server-derived hash of
                                  the canonical request payload.
                              content_hash:
                                type: string
                                description: Stable hash of the canonical send payload.
                              from_header:
                                type: string
                                description: |
                                  Raw `From:` header as sent on the wire, including any
                                  display name (e.g. `"Acme Support" <agent@acme.test>`).
                              from_address:
                                type: string
                                description: Bare email address parsed from `from_header`.
                              to_header:
                                type: string
                                description: |
                                  Raw `To:` header as sent on the wire, including any
                                  display name.
                              to_address:
                                type: string
                                description: Bare email address parsed from `to_header`.
                              subject:
                                type: string
                              body_size_bytes:
                                type: integer
                                description: |
                                  Total UTF-8 byte length of `body_text` + `body_html`.
                                  Surfaced on the list endpoint so callers can see "this
                                  row has a 4MB body" without fetching it.
                              content_discarded_at:
                                type:
                                  - string
                                  - "null"
                                format: date-time
                                description: |
                                  Timestamp at which the bodies were discarded by an
                                  entitlement-driven retention policy. Null when bodies
                                  are still present. The detail endpoint returns
                                  null-valued `body_text`/`body_html` for discarded rows.
                              message_id:
                                type:
                                  - string
                                  - "null"
                                description: |
                                  Wire-level Message-ID assigned to the outbound message
                                  (RFC 5322). Null on rows that never reached signing
                                  (queued, gate_denied, agent_failed before signing).
                              in_reply_to:
                                type:
                                  - string
                                  - "null"
                                description: |
                                  Wire-level In-Reply-To header value, when this send
                                  was a reply.
                              email_references:
                                type:
                                  - string
                                  - "null"
                                description: |
                                  Wire-level References header value, when this send
                                  was a reply.
                              in_reply_to_email_id:
                                type:
                                  - string
                                  - "null"
                                format: uuid
                                description: |
                                  Reference to the inbound `emails.id` that this send
                                  replied to, when known. Populated when the caller used
                                  /emails/{id}/reply or when /send-mail's `in_reply_to`
                                  matched a stored inbound message_id in the same org.
                              thread_id:
                                type:
                                  - string
                                  - "null"
                                format: uuid
                                description: |
                                  Conversation thread this send belongs to. A reply inherits
                                  the thread of the inbound it answers; a fresh send starts a
                                  new thread. Fetch `/threads/{thread_id}` for the full
                                  ordered thread (inbound + outbound interleaved). NULL on
                                  gate-denied sends and on sends created before threading was
                                  enabled.
                              queue_id:
                                type:
                                  - string
                                  - "null"
                                description: |
                                  Message identifier assigned by Primitive's outbound
                                  relay once the agent accepts the message. Null on
                                  queued, gate_denied, and agent_failed rows.
                              smtp_response_code:
                                type:
                                  - integer
                                  - "null"
                                description: |
                                  Receiver's 3-digit SMTP code (e.g. 250, 550, 451).
                                  Populated on terminal delivery statuses; may be null
                                  on a deferred where the agent never got an SMTP-level
                                  response (TCP refused, DNS failed, TLS handshake
                                  failed). `smtp_response_text` still carries Postfix's
                                  descriptive text in those cases.
                              smtp_response_text:
                                type:
                                  - string
                                  - "null"
                                description: |
                                  Free-form text portion of the receiver's SMTP
                                  response. The most useful debugging signal on a
                                  `bounced` or `deferred` row.
                              smtp_enhanced_status_code:
                                type:
                                  - string
                                  - "null"
                                description: |
                                  RFC 3463 enhanced status code (e.g. `5.1.1` for "Bad
                                  destination mailbox address"). Distinct from
                                  `smtp_response_code`: the basic 3-digit code is coarse
                                  (550 = "permanent failure"), the enhanced code is
                                  finer-grained.
                              dkim_selector:
                                type:
                                  - string
                                  - "null"
                                description: |
                                  DKIM selector used to sign the outbound message.
                                  Public DNS data; useful for diagnosing why a downstream
                                  verifier rejected the signature.
                              dkim_domain:
                                type:
                                  - string
                                  - "null"
                                description: DKIM signing domain.
                              error_code:
                                type:
                                  - string
                                  - "null"
                                description: |
                                  Stable public error code on `agent_failed` rows. The
                                  agent's internal codes are remapped to a stable public
                                  taxonomy (see `publicAgentError` in the server) so this
                                  field is safe to branch on across agent versions.
                              error_message:
                                type:
                                  - string
                                  - "null"
                                description: Free-form error message accompanying `error_code`.
                              gates:
                                type:
                                  - array
                                  - "null"
                                items:
                                  type: object
                                  properties:
                                    name:
                                      type: string
                                      enum:
                                        - send_to_confirmed_domains
                                        - send_to_known_addresses
                                      description: Public recipient-scope gate name that denied the send.
                                    reason:
                                      type: string
                                      enum:
                                        - domain_not_confirmed
                                        - recipient_unauthenticated
                                        - recipient_not_known
                                      description: Stable machine-readable denial reason.
                                    message:
                                      type: string
                                      description: Human-readable explanation of the gate denial.
                                    subject:
                                      type: string
                                      description: Domain or address the gate evaluated.
                                    fix:
                                      type: object
                                      properties:
                                        action:
                                          type: string
                                          enum:
                                            - confirm_domain
                                            - sender_must_fix_authentication
                                            - wait_for_inbound
                                          description: Suggested next action for the caller.
                                        subject:
                                          type: string
                                          description: Entity the action applies to.
                                      required:
                                        - action
                                        - subject
                                    docs_url:
                                      type: string
                                      description: Public docs URL with more context.
                                  required:
                                    - name
                                    - reason
                                    - message
                                    - subject
                                description: |
                                  Gate-denial detail on `gate_denied` rows. Mirrors the
                                  synchronous /send-mail 403 contract so a caller's
                                  GateDenial handler is the same across live denies and
                                  historical lookups. Null on every other status.
                              request_id:
                                type:
                                  - string
                                  - "null"
                                description: |
                                  Server-issued request identifier from the original
                                  /send-mail call. Surfaced as the `X-Request-Id`
                                  response header on the live send and recorded here
                                  for support escalation.
                            required:
                              - id
                              - status
                              - status_changed_at
                              - created_at
                              - updated_at
                              - content_hash
                              - from_header
                              - from_address
                              - to_header
                              - to_address
                              - subject
                              - body_size_bytes
                          - type: object
                            properties:
                              body_text:
                                type:
                                  - string
                                  - "null"
                                description: |
                                  Plain-text body sent on the wire. Null when the
                                  send carried only an HTML body, or when bodies have
                                  been discarded post-send (`content_discarded_at`
                                  set).
                              body_html:
                                type:
                                  - string
                                  - "null"
                                description: |
                                  HTML body sent on the wire. Null when the send
                                  carried only a plain-text body, or when bodies
                                  have been discarded post-send.
          headers:
            ratelimit-limit:
              description: Maximum number of requests allowed in the current window.
              schema:
                type: integer
                minimum: 1
                example: 120
            ratelimit-remaining:
              description: Remaining requests in the current window.
              schema:
                type: integer
                minimum: 0
                example: 118
            ratelimit-reset:
              description: Unix timestamp (seconds) when the current window resets.
              schema:
                type: integer
                example: 1700000060
            ratelimit-policy:
              description: Rate-limit policy in `limit;w=seconds` format, e.g. `120;w=60`.
              schema:
                type: string
                example: 120;w=60
        "400":
          $ref: "#/components/responses/ValidationError"
          description: Invalid request parameters
        "401":
          $ref: "#/components/responses/Unauthorized"
          description: Invalid or missing API key
        "404":
          $ref: "#/components/responses/NotFound"
          description: Resource not found
      security:
        - BearerAuth: []
  /threads/{id}:
    parameters:
      - name: id
        in: path
        required: true
        schema:
          type: string
          format: uuid
        description: Resource UUID
    get:
      operationId: getThread
      summary: Get a conversation thread by id
      description: |
        Returns a conversation thread: its metadata plus the inbound
        and outbound messages that belong to it, interleaved in time
        order (oldest first). A thread spans both received emails and
        your sends, so an agent can reconstruct an entire back-and-forth
        from one call instead of walking reply headers.

        Each message carries a `direction` (`inbound` | `outbound`) and
        an `id`; fetch the full message via `/emails/{id}` or
        `/sent-emails/{id}` accordingly. Bodies are omitted here to keep
        the thread view lightweight.

        Discover a thread id from the `thread_id` field on any email or
        sent-email (list or detail). The message list is capped; compare
        `message_count` against `messages.length` to detect truncation.
      tags:
        - Threads
      responses:
        "200":
          description: Thread detail
          content:
            application/json:
              schema:
                allOf:
                  - type: object
                    properties:
                      success:
                        type: boolean
                        const: true
                    required:
                      - success
                      - data
                  - type: object
                    properties:
                      data:
                        type: object
                        description: |
                          A conversation thread: its metadata plus the inbound and
                          outbound messages that belong to it, interleaved oldest-first.
                          Membership is the stored `thread_id` on each message. Bodies are
                          omitted here to keep the thread view lightweight; fetch
                          `/emails/{id}` or `/sent-emails/{id}` for a single message's
                          full content.
                        properties:
                          id:
                            type: string
                            format: uuid
                          subject:
                            type:
                              - string
                              - "null"
                            description: Normalized subject of the thread (Re/Fwd prefixes stripped).
                          root_message_id:
                            type:
                              - string
                              - "null"
                            description: Message-ID of the conversation root, when known.
                          message_count:
                            type: integer
                            description: |
                              Total messages in the thread. `messages` is capped (most
                              recent first, then re-sorted oldest-first), so
                              `message_count > messages.length` signals truncation.
                          first_message_at:
                            type:
                              - string
                              - "null"
                            format: date-time
                          last_message_at:
                            type:
                              - string
                              - "null"
                            format: date-time
                          created_at:
                            type: string
                            format: date-time
                          messages:
                            type: array
                            items:
                              type: object
                              description: One message in a thread (inbound or outbound).
                              properties:
                                direction:
                                  type: string
                                  enum:
                                    - inbound
                                    - outbound
                                  description: |
                                    `inbound` for a received email (`/emails/{id}`), `outbound`
                                    for a send (`/sent-emails/{id}`). Use it with `id` to fetch
                                    full content from the right endpoint.
                                id:
                                  type: string
                                  format: uuid
                                message_id:
                                  type:
                                    - string
                                    - "null"
                                from:
                                  type:
                                    - string
                                    - "null"
                                to:
                                  type:
                                    - string
                                    - "null"
                                subject:
                                  type:
                                    - string
                                    - "null"
                                status:
                                  type:
                                    - string
                                    - "null"
                                  description: Lifecycle status (an EmailStatus or SentEmailStatus value, per `direction`).
                                timestamp:
                                  type:
                                    - string
                                    - "null"
                                  format: date-time
                                  description: received_at for inbound, created_at for outbound.
                              required:
                                - direction
                                - id
                        required:
                          - id
                          - message_count
                          - created_at
                          - messages
          headers:
            ratelimit-limit:
              description: Maximum number of requests allowed in the current window.
              schema:
                type: integer
                minimum: 1
                example: 120
            ratelimit-remaining:
              description: Remaining requests in the current window.
              schema:
                type: integer
                minimum: 0
                example: 118
            ratelimit-reset:
              description: Unix timestamp (seconds) when the current window resets.
              schema:
                type: integer
                example: 1700000060
            ratelimit-policy:
              description: Rate-limit policy in `limit;w=seconds` format, e.g. `120;w=60`.
              schema:
                type: string
                example: 120;w=60
        "400":
          $ref: "#/components/responses/ValidationError"
          description: Invalid request parameters
        "401":
          $ref: "#/components/responses/Unauthorized"
          description: Invalid or missing API key
        "404":
          $ref: "#/components/responses/NotFound"
          description: Resource not found
      security:
        - BearerAuth: []
  /functions:
    get:
      operationId: listFunctions
      summary: List functions
      description: |
        Returns every active (non-deleted) function in the org, newest
        first. Each entry carries deploy status and timestamps. To
        inspect the source code or deploy errors, use `GET /functions/{id}`.
      tags:
        - Functions
      responses:
        "200":
          description: List of functions
          content:
            application/json:
              schema:
                allOf:
                  - type: object
                    properties:
                      success:
                        type: boolean
                        const: true
                    required:
                      - success
                      - data
                  - type: object
                    properties:
                      data:
                        type: array
                        items:
                          type: object
                          description: One row from the function listing.
                          properties:
                            id:
                              type: string
                              format: uuid
                              description: Function id, also the script name in the edge runtime.
                            name:
                              type: string
                              description: Slug-style name set on creation. Stable; cannot be changed.
                            deploy_status:
                              type: string
                              enum:
                                - pending
                                - deployed
                                - failed
                              description: |
                                Lifecycle state of the latest deploy attempt:
                                  * `pending` — deploy in flight; the runtime has not yet
                                    confirmed the new bundle is live.
                                  * `deployed` — the running edge handler is the latest code.
                                  * `failed` — the most recent deploy attempt failed; the
                                    previously-live code (if any) is still running. The
                                    `deploy_error` field carries the error message.
                            deployed_at:
                              type:
                                - string
                                - "null"
                              format: date-time
                              description: Timestamp of the most recent successful deploy. Null until the first deploy succeeds.
                            created_at:
                              type: string
                              format: date-time
                            updated_at:
                              type: string
                              format: date-time
                          required:
                            - id
                            - name
                            - deploy_status
                            - created_at
                            - updated_at
          headers:
            ratelimit-limit:
              description: Maximum number of requests allowed in the current window.
              schema:
                type: integer
                minimum: 1
                example: 120
            ratelimit-remaining:
              description: Remaining requests in the current window.
              schema:
                type: integer
                minimum: 0
                example: 118
            ratelimit-reset:
              description: Unix timestamp (seconds) when the current window resets.
              schema:
                type: integer
                example: 1700000060
            ratelimit-policy:
              description: Rate-limit policy in `limit;w=seconds` format, e.g. `120;w=60`.
              schema:
                type: string
                example: 120;w=60
        "401":
          $ref: "#/components/responses/Unauthorized"
          description: Invalid or missing API key
      security:
        - BearerAuth: []
    post:
      operationId: createFunction
      summary: Deploy a function
      description: |
        Creates and deploys a new function. The handler must be a single
        ESM module whose default export is an object with an async
        `fetch(request, env)` method (Workers-style). Primitive signs
        each delivery and forwards the `Primitive-Signature` header to
        the handler. Verify the raw request body with
        `PRIMITIVE_WEBHOOK_SECRET` before parsing JSON; after verification
        the request body parses to a webhook event whose `event` field is
        `email.received` for normal inbound mail, or a machine-mail type
        (`email.bounced`, `email.tls_report`, `email.dmarc_report`,
        `email.dmarc_failure`) for bounces and reports. Code is bundled
        before being uploaded; ship a single self-contained file rather
        than relying on external imports.

        **Code limits.** `code` is capped at 1 MiB UTF-8. `sourceMap`
        (optional) is capped at 5 MiB UTF-8, stored with each deployment
        attempt, and sent to the runtime so stack traces can resolve to
        original source files.

        **Routing.** On successful deploy, the function code is live
        in the runtime, but inbound mail will not reach it until at
        least one route is bound. Routes are managed from the Primitive
        dashboard. A `deploy_status` of `deployed` means the script is
        installed, not that the function is receiving mail. The
        internal runtime URL is not returned by the API and is not a
        customer-facing integration surface.

        **Secrets.** New functions ship with the managed secrets
        (`PRIMITIVE_WEBHOOK_SECRET`, `PRIMITIVE_API_KEY`,
        `PRIMITIVE_API_BASE_URL`) already bound. Add user-set secrets via
        `POST /functions/{id}/secrets`; secret writes only land in the
        running handler on the next redeploy.
      tags:
        - Functions
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              additionalProperties: false
              properties:
                name:
                  type: string
                  pattern: ^[a-z0-9_-]{1,64}$
                  description: |
                    Slug-style name. Lowercase letters, digits, hyphens, and
                    underscores. 1 to 64 characters. Must be unique within the
                    org; a 409 is returned on collision.
                code:
                  type: string
                  minLength: 1
                  maxLength: 1048576
                  description: |
                    Pre-built handler as a single ESM module. Up to 1 MiB UTF-8.
                    Must export a default `{ async fetch(req, env, ctx) { ... } }`
                    object. Provide either `code` or `files`, not both.
                sourceMap:
                  type: string
                  minLength: 1
                  maxLength: 5242880
                  description: |
                    Optional source map for the bundle. Up to 5 MiB UTF-8.
                    Stored with the deployment attempt and sent to the runtime
                    to symbolicate stack traces in the function's logs. Only
                    valid with `code`.
                files:
                  type: object
                  additionalProperties:
                    type: string
                  description: |
                    Source files for a managed build, as a map of path to file
                    contents (for example {"package.json": "...",
                    "src/index.ts": "..."}). Provide this INSTEAD of `code` to
                    have the server install dependencies and bundle the source
                    for the Workers runtime before deploying. Include a
                    package.json (its `dependencies` are installed). Provide
                    either `code` or `files`, not both.
              required:
                - name
      responses:
        "201":
          description: Function created and deployed
          content:
            application/json:
              schema:
                allOf:
                  - type: object
                    properties:
                      success:
                        type: boolean
                        const: true
                    required:
                      - success
                      - data
                  - type: object
                    properties:
                      data:
                        type: object
                        description: Returned by POST /functions on a successful deploy.
                        properties:
                          id:
                            type: string
                            format: uuid
                          name:
                            type: string
                          deploy_status:
                            type: string
                            enum:
                              - pending
                              - deployed
                              - failed
                            description: |
                              Lifecycle state of the latest deploy attempt:
                                * `pending` — deploy in flight; the runtime has not yet
                                  confirmed the new bundle is live.
                                * `deployed` — the running edge handler is the latest code.
                                * `failed` — the most recent deploy attempt failed; the
                                  previously-live code (if any) is still running. The
                                  `deploy_error` field carries the error message.
                        required:
                          - id
                          - name
                          - deploy_status
        "400":
          $ref: "#/components/responses/ValidationError"
          description: Invalid request parameters or customer-correctable deploy rejection
        "401":
          $ref: "#/components/responses/Unauthorized"
          description: Invalid or missing API key
        "409":
          $ref: "#/components/responses/Conflict"
          description: A function with this name already exists in the org
        "424":
          $ref: "#/components/responses/FailedDependency"
          description: Function deploy could not be completed; previously deployed code remains live
        "429":
          $ref: "#/components/responses/RateLimited"
          description: Function deploy could not be completed; previously deployed code remains live
        "503":
          $ref: "#/components/responses/ServiceUnavailable"
          description: Function deploy could not be completed; previously deployed code remains live
      security:
        - BearerAuth: []
      parameters:
        - name: Idempotency-Key
          in: header
          required: false
          description: Optional client-supplied idempotency key. Retrying a request with the same key returns the original result instead of performing the action a second time; if omitted the server derives one from the canonical payload hash. Safe to retry network failures without duplicating side effects.
          schema:
            type: string
            minLength: 1
            maxLength: 255
  /functions/{id}:
    parameters:
      - name: id
        in: path
        required: true
        schema:
          type: string
          format: uuid
        description: Resource UUID
    get:
      operationId: getFunction
      summary: Get a function
      description: |
        Returns the full record for a function, including its current
        source code and the deploy status / error from the most recent
        deploy attempt.
      tags:
        - Functions
      responses:
        "200":
          description: Function record
          content:
            application/json:
              schema:
                allOf:
                  - type: object
                    properties:
                      success:
                        type: boolean
                        const: true
                    required:
                      - success
                      - data
                  - type: object
                    properties:
                      data:
                        type: object
                        description: Full function record returned by GET / PUT.
                        properties:
                          id:
                            type: string
                            format: uuid
                          name:
                            type: string
                          code:
                            type: string
                            description: |
                              The bundled handler source. UTF-8 string up to 1 MiB. The
                              same value most recently passed as `code` to POST or PUT.
                          deploy_status:
                            type: string
                            enum:
                              - pending
                              - deployed
                              - failed
                            description: |
                              Lifecycle state of the latest deploy attempt:
                                * `pending` — deploy in flight; the runtime has not yet
                                  confirmed the new bundle is live.
                                * `deployed` — the running edge handler is the latest code.
                                * `failed` — the most recent deploy attempt failed; the
                                  previously-live code (if any) is still running. The
                                  `deploy_error` field carries the error message.
                          deploy_error:
                            type:
                              - string
                              - "null"
                            description: |
                              Error message from the most recent failed deploy, or null
                              after a successful deploy. Surface this to users to explain
                              a `failed` status without polling.
                          deployed_at:
                            type:
                              - string
                              - "null"
                            format: date-time
                          created_at:
                            type: string
                            format: date-time
                          updated_at:
                            type: string
                            format: date-time
                        required:
                          - id
                          - name
                          - code
                          - deploy_status
                          - created_at
                          - updated_at
          headers:
            ratelimit-limit:
              description: Maximum number of requests allowed in the current window.
              schema:
                type: integer
                minimum: 1
                example: 120
            ratelimit-remaining:
              description: Remaining requests in the current window.
              schema:
                type: integer
                minimum: 0
                example: 118
            ratelimit-reset:
              description: Unix timestamp (seconds) when the current window resets.
              schema:
                type: integer
                example: 1700000060
            ratelimit-policy:
              description: Rate-limit policy in `limit;w=seconds` format, e.g. `120;w=60`.
              schema:
                type: string
                example: 120;w=60
        "401":
          $ref: "#/components/responses/Unauthorized"
          description: Invalid or missing API key
        "404":
          $ref: "#/components/responses/NotFound"
          description: Resource not found
      security:
        - BearerAuth: []
    put:
      operationId: updateFunction
      summary: Update and redeploy a function
      description: |
        Replaces the function's source code with the body's `code` and
        triggers a redeploy. Same size limits as `POST /functions`.
        Use this verb to push secret writes into the running handler:
        passing the same `code` re-runs the deploy and refreshes the
        binding set with the latest values from the secrets table.

        On deploy failure, the previously-deployed code stays live; the
        runtime never serves a half-built bundle. The response uses
        `error.code` `deploy_failed`, and the function's `deploy_error`
        field carries the latest deploy error for dashboard/API reads.
      tags:
        - Functions
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              additionalProperties: false
              properties:
                code:
                  type: string
                  minLength: 1
                  maxLength: 1048576
                  description: New pre-built handler. Same rules as CreateFunctionInput.code. Provide either `code` or `files`, not both.
                sourceMap:
                  type: string
                  minLength: 1
                  maxLength: 5242880
                files:
                  type: object
                  additionalProperties:
                    type: string
                  description: |
                    Source files for a managed build, as a map of path to file
                    contents. Provide this INSTEAD of `code` to rebuild and
                    redeploy from source. Same rules as CreateFunctionInput.files.
                managedRelease:
                  type: object
                  additionalProperties: false
                  description: Strict exact-redeploy claim for a prepared managed-agent Function. Valid only with `code` and an unscoped organization API key. The uploaded code must match the prepared release runtime bundle exactly.
                  properties:
                    releaseId:
                      type: string
                      format: uuid
                      description: Prepared managed-agent release UUID.
                    expectedManifestHash:
                      type: string
                      pattern: ^[a-f0-9]{64}$
                      description: Exact lowercase SHA-256 manifest hash returned by release preparation.
                  required:
                    - releaseId
                    - expectedManifestHash
              required: []
      responses:
        "200":
          description: Updated function
          content:
            application/json:
              schema:
                allOf:
                  - type: object
                    properties:
                      success:
                        type: boolean
                        const: true
                    required:
                      - success
                      - data
                  - type: object
                    properties:
                      data:
                        type: object
                        description: Full function record returned by GET / PUT.
                        properties:
                          id:
                            type: string
                            format: uuid
                          name:
                            type: string
                          code:
                            type: string
                            description: |
                              The bundled handler source. UTF-8 string up to 1 MiB. The
                              same value most recently passed as `code` to POST or PUT.
                          deploy_status:
                            type: string
                            enum:
                              - pending
                              - deployed
                              - failed
                            description: |
                              Lifecycle state of the latest deploy attempt:
                                * `pending` — deploy in flight; the runtime has not yet
                                  confirmed the new bundle is live.
                                * `deployed` — the running edge handler is the latest code.
                                * `failed` — the most recent deploy attempt failed; the
                                  previously-live code (if any) is still running. The
                                  `deploy_error` field carries the error message.
                          deploy_error:
                            type:
                              - string
                              - "null"
                            description: |
                              Error message from the most recent failed deploy, or null
                              after a successful deploy. Surface this to users to explain
                              a `failed` status without polling.
                          deployed_at:
                            type:
                              - string
                              - "null"
                            format: date-time
                          created_at:
                            type: string
                            format: date-time
                          updated_at:
                            type: string
                            format: date-time
                        required:
                          - id
                          - name
                          - code
                          - deploy_status
                          - created_at
                          - updated_at
          headers:
            ratelimit-limit:
              description: Maximum number of requests allowed in the current window.
              schema:
                type: integer
                minimum: 1
                example: 120
            ratelimit-remaining:
              description: Remaining requests in the current window.
              schema:
                type: integer
                minimum: 0
                example: 118
            ratelimit-reset:
              description: Unix timestamp (seconds) when the current window resets.
              schema:
                type: integer
                example: 1700000060
            ratelimit-policy:
              description: Rate-limit policy in `limit;w=seconds` format, e.g. `120;w=60`.
              schema:
                type: string
                example: 120;w=60
        "400":
          $ref: "#/components/responses/ValidationError"
          description: Invalid request parameters or customer-correctable deploy rejection
        "401":
          $ref: "#/components/responses/Unauthorized"
          description: Invalid or missing API key
        "404":
          $ref: "#/components/responses/NotFound"
          description: Resource not found
        "424":
          $ref: "#/components/responses/FailedDependency"
          description: Function deploy could not be completed; previously deployed code remains live
        "429":
          $ref: "#/components/responses/RateLimited"
          description: Function deploy could not be completed; previously deployed code remains live
        "503":
          $ref: "#/components/responses/ServiceUnavailable"
          description: Function deploy could not be completed; previously deployed code remains live
      security:
        - BearerAuth: []
    delete:
      operationId: deleteFunction
      summary: Delete a function
      description: Delete a hosted inbound function. It stops running on future inbound messages.
      tags:
        - Functions
      responses:
        "200":
          description: Resource deleted
          content:
            application/json:
              schema:
                allOf:
                  - type: object
                    properties:
                      success:
                        type: boolean
                        const: true
                    required:
                      - success
                      - data
                  - type: object
                    properties:
                      data:
                        type: object
                        properties:
                          deleted:
                            type: boolean
                            const: true
                        required:
                          - deleted
          headers:
            ratelimit-limit:
              description: Maximum number of requests allowed in the current window.
              schema:
                type: integer
                minimum: 1
                example: 120
            ratelimit-remaining:
              description: Remaining requests in the current window.
              schema:
                type: integer
                minimum: 0
                example: 118
            ratelimit-reset:
              description: Unix timestamp (seconds) when the current window resets.
              schema:
                type: integer
                example: 1700000060
            ratelimit-policy:
              description: Rate-limit policy in `limit;w=seconds` format, e.g. `120;w=60`.
              schema:
                type: string
                example: 120;w=60
        "401":
          $ref: "#/components/responses/Unauthorized"
          description: Invalid or missing API key
        "404":
          $ref: "#/components/responses/NotFound"
          description: Resource not found
        "502":
          $ref: "#/components/responses/BadGateway"
          description: Primitive could not complete the downstream SMTP request
      security:
        - BearerAuth: []
  /functions/{id}/test:
    parameters:
      - name: id
        in: path
        required: true
        schema:
          type: string
          format: uuid
        description: Resource UUID
    post:
      operationId: testFunction
      summary: Send a test invocation
      description: |
        Sends a real test email from a Primitive-controlled sender to a
        local-part on one of the org's verified inbound domains. By
        default the recipient is a synthetic
        `__primitive_function_test+<random>@<domain>` address on a
        domain selected to route to the function. Scoped functions use
        their scoped domain; fallback functions use a domain that has
        no enabled domain-scoped endpoint. Pass `local_part` to
        override and exercise routing logic that branches on a specific
        recipient (the common pattern when one function handles multiple
        inboxes like `summarize@` and `action@`). The function fires
        through the normal MX delivery path, so reply / send-mail calls
        from inside the handler against the inbound's `email.id` work
        the same as in production. Returns immediately after the send is
        queued; the invocation appears on the function's invocations
        list within a few seconds.

        Requires that the function is currently `deployed`. Returns 422
        if the function is in `pending` or `failed` state, or if the
        org has no verified inbound domain to receive the test mail.
        Returns 400 if `local_part` is set to a value that does not
        match the local-part character set.
      tags:
        - Functions
      requestBody:
        required: false
        content:
          application/json:
            schema:
              type: object
              properties:
                local_part:
                  type: string
                  description: |
                    Override the synthetic local-part. When set, the
                    test email is sent to `<local_part>@<picked-domain>`
                    instead of the default
                    `__primitive_function_test+<random>@<picked-domain>`.
                    Must start with an alphanumeric and contain only
                    letters, digits, dots, plus signs, hyphens, or
                    underscores; 1-64 characters total.
                  minLength: 1
                  maxLength: 64
                  pattern: ^[A-Za-z0-9][A-Za-z0-9._+-]{0,63}$
                  example: summarize
      responses:
        "200":
          description: Test send queued
          content:
            application/json:
              schema:
                allOf:
                  - type: object
                    properties:
                      success:
                        type: boolean
                        const: true
                    required:
                      - success
                      - data
                  - type: object
                    properties:
                      data:
                        type: object
                        description: |
                          Metadata returned by POST /functions/{id}/test. The send is
                          queued; poll `trace_url` to watch the run progress through
                          send -> inbound -> webhook deliveries -> outbound requests,
                          logs, and replies.
                        properties:
                          test_run_id:
                            type: string
                            format: uuid
                            description: Durable test run id used to fetch the run trace.
                          inbound_domain:
                            type: string
                            description: Verified inbound domain the test email was sent to.
                          to:
                            type: string
                            description: Synthetic local-part plus inbound_domain. Visible in the org's inbox.
                          from:
                            type: string
                            description: Primitive-controlled outbound sender used for the test.
                          send_id:
                            type: string
                            description: |
                              Outbound message id from the underlying send. NOT the
                              inbound email's id; the inbound id is created when the
                              email arrives via MX and lands on the function's
                              invocations list.
                          subject:
                            type: string
                            description: Subject placed on the test email so it can be located in the inbox.
                          poll_since:
                            type: string
                            format: date-time
                            description: |
                              ISO timestamp suitable as a `since` lower bound when
                              polling /emails for the inbound's arrival. Captured
                              slightly before the send to absorb light clock skew.
                          watch_url:
                            type: string
                            format: uri
                            description: Function detail page where invocations show up live.
                          trace_url:
                            type: string
                            description: Relative API URL for GET /functions/{id}/test-runs/{test_run_id}/trace.
                        required:
                          - test_run_id
                          - inbound_domain
                          - to
                          - from
                          - send_id
                          - subject
                          - poll_since
                          - watch_url
                          - trace_url
          headers:
            ratelimit-limit:
              description: Maximum number of requests allowed in the current window.
              schema:
                type: integer
                minimum: 1
                example: 120
            ratelimit-remaining:
              description: Remaining requests in the current window.
              schema:
                type: integer
                minimum: 0
                example: 118
            ratelimit-reset:
              description: Unix timestamp (seconds) when the current window resets.
              schema:
                type: integer
                example: 1700000060
            ratelimit-policy:
              description: Rate-limit policy in `limit;w=seconds` format, e.g. `120;w=60`.
              schema:
                type: string
                example: 120;w=60
        "400":
          $ref: "#/components/responses/ValidationError"
          description: Invalid request parameters
        "401":
          $ref: "#/components/responses/Unauthorized"
          description: Invalid or missing API key
        "404":
          $ref: "#/components/responses/NotFound"
          description: Resource not found
        "422":
          $ref: "#/components/responses/UnprocessableEntity"
          description: Function not in a state that can be invoked, or no inbound domain configured
        "502":
          $ref: "#/components/responses/BadGateway"
          description: Primitive could not complete the downstream SMTP request
        "503":
          $ref: "#/components/responses/ServiceUnavailable"
          description: Sending agent misconfigured
      security:
        - BearerAuth: []
      parameters:
        - name: Idempotency-Key
          in: header
          required: false
          description: Optional client-supplied idempotency key. Retrying a request with the same key returns the original result instead of performing the action a second time; if omitted the server derives one from the canonical payload hash. Safe to retry network failures without duplicating side effects.
          schema:
            type: string
            minLength: 1
            maxLength: 255
  /functions/{id}/test-runs/{run_id}/trace:
    parameters:
      - name: id
        in: path
        required: true
        schema:
          type: string
          format: uuid
        description: Resource UUID
      - name: run_id
        in: path
        required: true
        description: Function test run id returned by POST /functions/{id}/test.
        schema:
          type: string
          format: uuid
    get:
      operationId: getFunctionTestRunTrace
      summary: Get a function test run trace
      description: |
        Returns the current end-to-end trace for a function test run.
        The trace is intentionally partial while the test is still in
        flight: callers can poll this endpoint and watch it fill in
        from send -> inbound -> webhook deliveries -> outbound
        requests, logs, and replies.
      tags:
        - Functions
      responses:
        "200":
          description: Function test run trace
          content:
            application/json:
              schema:
                allOf:
                  - type: object
                    properties:
                      success:
                        type: boolean
                        const: true
                    required:
                      - success
                      - data
                  - type: object
                    properties:
                      data:
                        type: object
                        description: |
                          End-to-end trace for a `POST /functions/{id}/test` run. The
                          shape is stable, but many nested sections are null or empty
                          until the corresponding phase has happened.
                        properties:
                          state:
                            type: string
                            description: |
                              High-level state for a function test run trace:
                                - `send_failed`: the initial test email send failed.
                                - `waiting_for_send`: the test run was created but no send result has been recorded yet.
                                - `waiting_for_inbound`: the test send was queued and the matching inbound email has not arrived yet.
                                - `waiting_for_function`: the inbound email arrived and webhook/function processing is still in flight.
                                - `completed`: the function webhook completed successfully.
                                - `failed`: webhook delivery exhausted retries.
                            enum:
                              - send_failed
                              - waiting_for_send
                              - waiting_for_inbound
                              - waiting_for_function
                              - completed
                              - failed
                          test_run:
                            type: object
                            properties:
                              id:
                                type: string
                                format: uuid
                              function_id:
                                type: string
                                format: uuid
                              inbound_domain:
                                type: string
                              to:
                                type: string
                              from:
                                type: string
                              subject:
                                type: string
                              poll_since:
                                type: string
                                format: date-time
                              created_at:
                                type: string
                                format: date-time
                              sent_at:
                                type:
                                  - string
                                  - "null"
                                format: date-time
                              send_error:
                                type:
                                  - string
                                  - "null"
                            required:
                              - id
                              - function_id
                              - inbound_domain
                              - to
                              - from
                              - subject
                              - poll_since
                              - created_at
                              - sent_at
                              - send_error
                          test_send:
                            type:
                              - object
                              - "null"
                            properties:
                              id:
                                type: string
                                format: uuid
                              status:
                                type: string
                                description: |
                                  Lifecycle status of a sent_emails row. Possible values:

                                    - `queued`: pre-call INSERT; the outbound agent has not
                                      yet replied.
                                    - `submitted_to_agent`: agent accepted; `queue_id` is set.
                                    - `agent_failed`: agent rejected; `error_code` and
                                      `error_message` carry the reason.
                                    - `gate_denied`: a recipient-scope gate denied the send;
                                      the agent was never called. The `gates` array carries
                                      the denial detail. /send-mail returns 403 in this case
                                      so callers see the denial synchronously; /sent-emails
                                      additionally records the row for historical lookup,
                                      which is when this status appears in a listing.
                                    - `unknown`: terminal indeterminate; the on-box log
                                      poller couldn't classify the receiver's response.
                                    - `delivered` / `bounced` / `deferred` / `wait_timeout`:
                                      terminal delivery outcomes (see DeliveryStatus).
                                enum:
                                  - queued
                                  - submitted_to_agent
                                  - agent_failed
                                  - gate_denied
                                  - unknown
                                  - delivered
                                  - bounced
                                  - deferred
                                  - wait_timeout
                              queue_id:
                                type:
                                  - string
                                  - "null"
                              created_at:
                                type: string
                                format: date-time
                              updated_at:
                                type: string
                                format: date-time
                            required:
                              - id
                              - status
                              - queue_id
                              - created_at
                              - updated_at
                          inbound_email:
                            type:
                              - object
                              - "null"
                            properties:
                              id:
                                type: string
                                format: uuid
                              status:
                                type: string
                                description: |
                                  Lifecycle status of an INBOUND email (a row in the `emails`
                                  table). Distinct from `SentEmailStatus`, which describes
                                  the OUTBOUND lifecycle (the `sent_emails` table) and uses
                                  a different vocabulary because the lifecycles differ.
                                  Possible values:

                                    - `pending`: the row was inserted at ingestion (mx_main)
                                      and has not yet completed the spam / filter / auth
                                      pipeline. Body and parsed fields are present; webhook
                                      delivery is not yet scheduled. Most rows transition out
                                      of `pending` within seconds.
                                    - `accepted`: the inbound passed the policy gates and is
                                      queued for webhook delivery. The `webhook_status` field
                                      tracks the separate webhook-delivery lifecycle from
                                      this point.
                                    - `completed`: terminal success. Webhook delivery
                                      attempted and acknowledged by every active endpoint, OR
                                      no endpoints are configured, so the row is durably
                                      archived.
                                    - `rejected`: terminal failure at ingestion (spam, blocked
                                      sender, filter rule, malformed). The body and metadata
                                      are stored for auditing but no webhook fires and the
                                      row is not repliable.

                                  See also `webhook_status` (separate enum tracking the
                                  webhook-delivery state machine) and `SentEmailStatus` (the
                                  outbound vocabulary).
                                enum:
                                  - pending
                                  - accepted
                                  - completed
                                  - rejected
                              received_at:
                                type: string
                                format: date-time
                              from:
                                type: string
                              to:
                                type: string
                              subject:
                                type:
                                  - string
                                  - "null"
                              webhook_status:
                                type:
                                  - string
                                  - "null"
                                description: |
                                  Webhook-delivery state for an inbound email. Tracks a
                                  SEPARATE lifecycle from the email's `status` field; the
                                  same row carries both. Possible values:

                                    - `pending`: ingestion is past `pending` (the email itself
                                      is `accepted`) but the webhook fan-out has not yet
                                      started for this row.
                                    - `in_flight`: at least one delivery attempt is in flight.
                                    - `fired`: terminal success. Every active endpoint
                                      acknowledged the delivery (or accepted it after retries).
                                    - `failed`: terminal partial-failure. At least one endpoint
                                      exhausted its retry budget; some endpoints may still
                                      have succeeded.
                                    - `exhausted`: terminal failure. Every endpoint exhausted
                                      its retry budget without success.
                                    - `null`: no endpoints configured, so no webhook lifecycle
                                      applies.

                                  Note that the value `pending` here does NOT mean the email
                                  is `pending`; it means the email is past ingestion but
                                  webhook delivery has not yet begun. Two overlapping uses
                                  of the word `pending` for distinct lifecycle phases.
                                enum:
                                  - pending
                                  - in_flight
                                  - fired
                                  - failed
                                  - exhausted
                                  - null
                              webhook_attempt_count:
                                type: integer
                              webhook_last_status_code:
                                type:
                                  - integer
                                  - "null"
                              webhook_last_error:
                                type:
                                  - string
                                  - "null"
                            required:
                              - id
                              - status
                              - received_at
                              - from
                              - to
                              - subject
                              - webhook_status
                              - webhook_attempt_count
                              - webhook_last_status_code
                              - webhook_last_error
                          deliveries:
                            type: array
                            items:
                              type: object
                              properties:
                                id:
                                  type: string
                                  description: Webhook delivery id.
                                endpoint_id:
                                  type: string
                                  format: uuid
                                endpoint_url:
                                  type: string
                                  format: uri
                                status:
                                  type: string
                                  enum:
                                    - pending
                                    - delivered
                                    - header_confirmed
                                    - failed
                                attempt_count:
                                  type: integer
                                duration_ms:
                                  type:
                                    - integer
                                    - "null"
                                last_error:
                                  type:
                                    - string
                                    - "null"
                                last_error_code:
                                  type:
                                    - string
                                    - "null"
                                created_at:
                                  type: string
                                  format: date-time
                                updated_at:
                                  type: string
                                  format: date-time
                                endpoint:
                                  type:
                                    - object
                                    - "null"
                                  properties:
                                    id:
                                      type: string
                                      format: uuid
                                    kind:
                                      type: string
                                      description: Endpoint kind. Current traces may include `http` or `function`; future endpoint kinds may appear.
                                    function_id:
                                      type:
                                        - string
                                        - "null"
                                      format: uuid
                                    function_name:
                                      type:
                                        - string
                                        - "null"
                                    domain_id:
                                      type:
                                        - string
                                        - "null"
                                      format: uuid
                                    enabled:
                                      type: boolean
                                    deactivated_at:
                                      type:
                                        - string
                                        - "null"
                                      format: date-time
                                    is_current_function:
                                      type: boolean
                                  required:
                                    - id
                                    - kind
                                    - function_id
                                    - function_name
                                    - domain_id
                                    - enabled
                                    - deactivated_at
                                    - is_current_function
                              required:
                                - id
                                - endpoint_id
                                - endpoint_url
                                - status
                                - attempt_count
                                - duration_ms
                                - last_error
                                - last_error_code
                                - created_at
                                - updated_at
                                - endpoint
                          outbound_requests:
                            type: array
                            items:
                              type: object
                              properties:
                                id:
                                  type: string
                                  format: uuid
                                function_id:
                                  type: string
                                  format: uuid
                                webhook_delivery_id:
                                  type:
                                    - string
                                    - "null"
                                email_id:
                                  type:
                                    - string
                                    - "null"
                                  format: uuid
                                endpoint_id:
                                  type:
                                    - string
                                    - "null"
                                  format: uuid
                                method:
                                  type: string
                                url:
                                  type: string
                                  format: uri
                                host:
                                  type: string
                                path:
                                  type: string
                                status_code:
                                  type:
                                    - integer
                                    - "null"
                                ok:
                                  type:
                                    - boolean
                                    - "null"
                                duration_ms:
                                  type: integer
                                error:
                                  type:
                                    - string
                                    - "null"
                                ts:
                                  type: string
                                  format: date-time
                              required:
                                - id
                                - function_id
                                - webhook_delivery_id
                                - email_id
                                - endpoint_id
                                - method
                                - url
                                - host
                                - path
                                - status_code
                                - ok
                                - duration_ms
                                - error
                                - ts
                          logs:
                            type: array
                            items:
                              type: object
                              description: |
                                One row from GET /functions/{id}/logs. Represents a single
                                captured log line emitted by the running handler (e.g. via
                                `console.log` / `console.error`).
                              properties:
                                id:
                                  type: string
                                  format: uuid
                                  description: Unique log row id (stable across pages).
                                function_id:
                                  type: string
                                  format: uuid
                                  description: The function this log row belongs to.
                                level:
                                  type: string
                                  enum:
                                    - debug
                                    - log
                                    - info
                                    - warn
                                    - error
                                  description: |
                                    Severity. `log` is the runtime's default for unannotated
                                    `console.log` calls; the other levels match standard
                                    `console.*` methods.
                                message:
                                  type: string
                                  description: |
                                    The textual message body. The runtime stringifies non-string
                                    arguments before persisting, so this is always a plain
                                    string.
                                ts:
                                  type: string
                                  format: date-time
                                  description: |
                                    When the handler emitted this line. Newest-first ordering
                                    on this column drives pagination; clock is the runtime's,
                                    not the gateway's.
                                metadata:
                                  type:
                                    - object
                                    - "null"
                                  additionalProperties: true
                                  description: |
                                    Optional structured payload the runtime attaches alongside
                                    the message (e.g. extra args passed to `console.log`).
                                    Shape is opaque; treat keys as untyped.
                              required:
                                - id
                                - function_id
                                - level
                                - message
                                - ts
                          replies:
                            type: array
                            items:
                              type: object
                              properties:
                                id:
                                  type: string
                                  format: uuid
                                status:
                                  type: string
                                  description: |
                                    Lifecycle status of a sent_emails row. Possible values:

                                      - `queued`: pre-call INSERT; the outbound agent has not
                                        yet replied.
                                      - `submitted_to_agent`: agent accepted; `queue_id` is set.
                                      - `agent_failed`: agent rejected; `error_code` and
                                        `error_message` carry the reason.
                                      - `gate_denied`: a recipient-scope gate denied the send;
                                        the agent was never called. The `gates` array carries
                                        the denial detail. /send-mail returns 403 in this case
                                        so callers see the denial synchronously; /sent-emails
                                        additionally records the row for historical lookup,
                                        which is when this status appears in a listing.
                                      - `unknown`: terminal indeterminate; the on-box log
                                        poller couldn't classify the receiver's response.
                                      - `delivered` / `bounced` / `deferred` / `wait_timeout`:
                                        terminal delivery outcomes (see DeliveryStatus).
                                  enum:
                                    - queued
                                    - submitted_to_agent
                                    - agent_failed
                                    - gate_denied
                                    - unknown
                                    - delivered
                                    - bounced
                                    - deferred
                                    - wait_timeout
                                to:
                                  type: string
                                subject:
                                  type: string
                                queue_id:
                                  type:
                                    - string
                                    - "null"
                                created_at:
                                  type: string
                                  format: date-time
                              required:
                                - id
                                - status
                                - to
                                - subject
                                - queue_id
                                - created_at
                        required:
                          - state
                          - test_run
                          - test_send
                          - inbound_email
                          - deliveries
                          - outbound_requests
                          - logs
                          - replies
          headers:
            ratelimit-limit:
              description: Maximum number of requests allowed in the current window.
              schema:
                type: integer
                minimum: 1
                example: 120
            ratelimit-remaining:
              description: Remaining requests in the current window.
              schema:
                type: integer
                minimum: 0
                example: 118
            ratelimit-reset:
              description: Unix timestamp (seconds) when the current window resets.
              schema:
                type: integer
                example: 1700000060
            ratelimit-policy:
              description: Rate-limit policy in `limit;w=seconds` format, e.g. `120;w=60`.
              schema:
                type: string
                example: 120;w=60
        "400":
          $ref: "#/components/responses/ValidationError"
          description: Invalid request parameters
        "401":
          $ref: "#/components/responses/Unauthorized"
          description: Invalid or missing API key
        "403":
          $ref: "#/components/responses/Forbidden"
          description: Authenticated caller lacks permission for the operation
        "404":
          $ref: "#/components/responses/NotFound"
          description: Resource not found
      security:
        - BearerAuth: []
  /functions/routing-topology:
    get:
      operationId: getOrgRoutingTopology
      summary: Get the org's function routing topology
      description: |
        Returns a single snapshot of how inbound mail is routed across
        this org's active domains and functions: which active domain has
        which function bound, the org's fallback function (if any), and
        every deployed function with no route bound. Use this to answer
        "which of my functions actually receive mail?" diagnostically.
      tags:
        - Functions
      responses:
        "200":
          description: Routing topology
          content:
            application/json:
              schema:
                allOf:
                  - type: object
                    properties:
                      success:
                        type: boolean
                        const: true
                    required:
                      - success
                      - data
                  - type: object
                    properties:
                      data:
                        type: object
                        description: |
                          Org-wide map of function routing: which domain points at which
                          function, the org's fallback binding (if any), and every
                          deployed function with no route currently bound.
                        properties:
                          domains:
                            type: array
                            items:
                              type: object
                              properties:
                                domain_id:
                                  type: string
                                  format: uuid
                                domain:
                                  type: string
                                routed_function:
                                  type:
                                    - object
                                    - "null"
                                  properties:
                                    id:
                                      type: string
                                      format: uuid
                                    name:
                                      type: string
                                  required:
                                    - id
                                    - name
                                endpoint_enabled:
                                  type:
                                    - boolean
                                    - "null"
                              required:
                                - domain_id
                                - domain
                                - routed_function
                                - endpoint_enabled
                          fallback_function:
                            type:
                              - object
                              - "null"
                            properties:
                              id:
                                type: string
                                format: uuid
                              name:
                                type: string
                            required:
                              - id
                              - name
                          fallback_enabled:
                            type:
                              - boolean
                              - "null"
                          unrouted_functions:
                            type: array
                            items:
                              type: object
                              properties:
                                id:
                                  type: string
                                  format: uuid
                                name:
                                  type: string
                              required:
                                - id
                                - name
                        required:
                          - domains
                          - fallback_function
                          - fallback_enabled
                          - unrouted_functions
          headers:
            ratelimit-limit:
              description: Maximum number of requests allowed in the current window.
              schema:
                type: integer
                minimum: 1
                example: 120
            ratelimit-remaining:
              description: Remaining requests in the current window.
              schema:
                type: integer
                minimum: 0
                example: 118
            ratelimit-reset:
              description: Unix timestamp (seconds) when the current window resets.
              schema:
                type: integer
                example: 1700000060
            ratelimit-policy:
              description: Rate-limit policy in `limit;w=seconds` format, e.g. `120;w=60`.
              schema:
                type: string
                example: 120;w=60
        "401":
          $ref: "#/components/responses/Unauthorized"
          description: Invalid or missing API key
        "403":
          $ref: "#/components/responses/Forbidden"
          description: Authenticated caller lacks permission for the operation
      security:
        - BearerAuth: []
  /functions/{id}/routing:
    parameters:
      - name: id
        in: path
        required: true
        schema:
          type: string
          format: uuid
        description: Resource UUID
    get:
      operationId: getFunctionRouting
      summary: Get a function's current route binding
      description: |
        Returns the endpoint binding for the function, or null when no
        route is currently bound. The binding identifies whether the
        function receives mail for a specific domain (scoped) or for any
        active domain that has no scoped binding (fallback).
      tags:
        - Functions
      responses:
        "200":
          description: Function routing
          content:
            application/json:
              schema:
                allOf:
                  - type: object
                    properties:
                      success:
                        type: boolean
                        const: true
                    required:
                      - success
                      - data
                  - type: object
                    properties:
                      data:
                        oneOf:
                          - type: object
                            description: |
                              A single route binding for a function. `domain` is null when the
                              binding is the org's fallback (any active domain without a scoped
                              binding); otherwise it carries the scoped domain. `rules` is
                              reserved for future routing predicates.
                            properties:
                              endpoint_id:
                                type: string
                                format: uuid
                              enabled:
                                type: boolean
                              domain:
                                type:
                                  - object
                                  - "null"
                                properties:
                                  id:
                                    type: string
                                    format: uuid
                                  name:
                                    type:
                                      - string
                                      - "null"
                                required:
                                  - id
                              rules:
                                type: object
                                description: Future routing predicates. Currently empty.
                              delivery_count:
                                type: integer
                              success_count:
                                type: integer
                              failure_count:
                                type: integer
                              consecutive_fails:
                                type: integer
                              last_delivery_at:
                                type:
                                  - string
                                  - "null"
                                format: date-time
                              last_success_at:
                                type:
                                  - string
                                  - "null"
                                format: date-time
                              last_failure_at:
                                type:
                                  - string
                                  - "null"
                                format: date-time
                            required:
                              - endpoint_id
                              - enabled
                              - domain
                              - rules
                          - type: "null"
          headers:
            ratelimit-limit:
              description: Maximum number of requests allowed in the current window.
              schema:
                type: integer
                minimum: 1
                example: 120
            ratelimit-remaining:
              description: Remaining requests in the current window.
              schema:
                type: integer
                minimum: 0
                example: 118
            ratelimit-reset:
              description: Unix timestamp (seconds) when the current window resets.
              schema:
                type: integer
                example: 1700000060
            ratelimit-policy:
              description: Rate-limit policy in `limit;w=seconds` format, e.g. `120;w=60`.
              schema:
                type: string
                example: 120;w=60
        "401":
          $ref: "#/components/responses/Unauthorized"
          description: Invalid or missing API key
        "404":
          $ref: "#/components/responses/NotFound"
          description: Resource not found
      security:
        - BearerAuth: []
  /functions/{id}/route:
    parameters:
      - name: id
        in: path
        required: true
        schema:
          type: string
          format: uuid
        description: Resource UUID
    put:
      operationId: setFunctionRoute
      summary: Bind a route to a function
      description: |
        Binds inbound mail to this function. The route target is either
        a specific verified domain (scoped) or the org's fallback (any
        active domain with no scoped binding). If another function is
        already bound at the target, returns a `conflict` envelope
        describing the holder; re-issue with `takeover: true` to
        deactivate that prior binding and install this one.
      tags:
        - Functions
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              description: |
                Target for a route binding. Either a specific verified domain
                (scoped) or the org-wide fallback. Pass `takeover: true` to
                deactivate any conflicting binding before installing this one.
              properties:
                target:
                  oneOf:
                    - type: object
                      properties:
                        kind:
                          type: string
                          enum:
                            - domain
                        domainId:
                          type: string
                          format: uuid
                      required:
                        - kind
                        - domainId
                    - type: object
                      properties:
                        kind:
                          type: string
                          enum:
                            - fallback
                      required:
                        - kind
                takeover:
                  type: boolean
                  description: When true, deactivate any conflicting binding before installing this one.
              required:
                - target
      responses:
        "200":
          description: Route bound, or conflict requiring takeover
          content:
            application/json:
              schema:
                allOf:
                  - type: object
                    properties:
                      success:
                        type: boolean
                        const: true
                    required:
                      - success
                      - data
                  - type: object
                    properties:
                      data:
                        type: object
                        description: |
                          On success, carries the new `routing`. On conflict, carries
                          `conflict` describing the binding holder so the caller can
                          re-issue with `takeover: true`.
                        properties:
                          routing:
                            oneOf:
                              - type: object
                                description: |
                                  A single route binding for a function. `domain` is null when the
                                  binding is the org's fallback (any active domain without a scoped
                                  binding); otherwise it carries the scoped domain. `rules` is
                                  reserved for future routing predicates.
                                properties:
                                  endpoint_id:
                                    type: string
                                    format: uuid
                                  enabled:
                                    type: boolean
                                  domain:
                                    type:
                                      - object
                                      - "null"
                                    properties:
                                      id:
                                        type: string
                                        format: uuid
                                      name:
                                        type:
                                          - string
                                          - "null"
                                    required:
                                      - id
                                  rules:
                                    type: object
                                    description: Future routing predicates. Currently empty.
                                  delivery_count:
                                    type: integer
                                  success_count:
                                    type: integer
                                  failure_count:
                                    type: integer
                                  consecutive_fails:
                                    type: integer
                                  last_delivery_at:
                                    type:
                                      - string
                                      - "null"
                                    format: date-time
                                  last_success_at:
                                    type:
                                      - string
                                      - "null"
                                    format: date-time
                                  last_failure_at:
                                    type:
                                      - string
                                      - "null"
                                    format: date-time
                                required:
                                  - endpoint_id
                                  - enabled
                                  - domain
                                  - rules
                              - type: "null"
                          conflict:
                            type: object
                            properties:
                              kind:
                                type: string
                                enum:
                                  - http
                                  - function
                              functionId:
                                type:
                                  - string
                                  - "null"
                                format: uuid
                              functionName:
                                type:
                                  - string
                                  - "null"
                              url:
                                type:
                                  - string
                                  - "null"
                            required:
                              - kind
          headers:
            ratelimit-limit:
              description: Maximum number of requests allowed in the current window.
              schema:
                type: integer
                minimum: 1
                example: 120
            ratelimit-remaining:
              description: Remaining requests in the current window.
              schema:
                type: integer
                minimum: 0
                example: 118
            ratelimit-reset:
              description: Unix timestamp (seconds) when the current window resets.
              schema:
                type: integer
                example: 1700000060
            ratelimit-policy:
              description: Rate-limit policy in `limit;w=seconds` format, e.g. `120;w=60`.
              schema:
                type: string
                example: 120;w=60
        "400":
          $ref: "#/components/responses/ValidationError"
          description: Invalid request parameters
        "401":
          $ref: "#/components/responses/Unauthorized"
          description: Invalid or missing API key
        "404":
          $ref: "#/components/responses/NotFound"
          description: Resource not found
      security:
        - BearerAuth: []
    delete:
      operationId: unsetFunctionRoute
      summary: Unbind any route from a function
      description: |
        Deactivates every active endpoint bound to this function. The
        function stays deployed but stops receiving inbound mail. Safe
        to call when no route is currently bound (no-op).
      tags:
        - Functions
      responses:
        "200":
          description: Route unbound
          content:
            application/json:
              schema:
                allOf:
                  - type: object
                    properties:
                      success:
                        type: boolean
                        const: true
                    required:
                      - success
                      - data
                  - type: object
                    properties:
                      data:
                        type: object
                        properties:
                          unrouted:
                            type: boolean
                            enum:
                              - true
                        required:
                          - unrouted
          headers:
            ratelimit-limit:
              description: Maximum number of requests allowed in the current window.
              schema:
                type: integer
                minimum: 1
                example: 120
            ratelimit-remaining:
              description: Remaining requests in the current window.
              schema:
                type: integer
                minimum: 0
                example: 118
            ratelimit-reset:
              description: Unix timestamp (seconds) when the current window resets.
              schema:
                type: integer
                example: 1700000060
            ratelimit-policy:
              description: Rate-limit policy in `limit;w=seconds` format, e.g. `120;w=60`.
              schema:
                type: string
                example: 120;w=60
        "401":
          $ref: "#/components/responses/Unauthorized"
          description: Invalid or missing API key
        "404":
          $ref: "#/components/responses/NotFound"
          description: Resource not found
      security:
        - BearerAuth: []
  /functions/{id}/secrets:
    parameters:
      - name: id
        in: path
        required: true
        schema:
          type: string
          format: uuid
        description: Resource UUID
    get:
      operationId: listFunctionSecrets
      summary: List a function's secrets
      description: |
        Returns metadata for every secret bound to the function, with
        managed entries (provisioned by Primitive) listed first and
        user-set entries listed alphabetically after. **Values are
        never returned.** Secret writes are write-only.

        Managed entries (e.g. `PRIMITIVE_WEBHOOK_SECRET`,
        `PRIMITIVE_API_KEY`, `PRIMITIVE_API_BASE_URL`) carry a
        `description` instead of `created_at` / `updated_at`. They
        cannot be created, updated, or deleted via this API.
      tags:
        - Functions
      responses:
        "200":
          description: List of secrets (metadata only, no values)
          content:
            application/json:
              schema:
                allOf:
                  - type: object
                    properties:
                      success:
                        type: boolean
                        const: true
                    required:
                      - success
                      - data
                  - type: object
                    properties:
                      data:
                        type: object
                        properties:
                          items:
                            type: array
                            items:
                              type: object
                              description: |
                                One row from GET /functions/{id}/secrets. Discriminate on the
                                `managed` field:
                                  * `managed = true`  — system secret provisioned by Primitive.
                                    `description` is set; `created_at` / `updated_at` are
                                    null because the row is virtual (resolved at deploy time
                                    from the managed registry, not stored in the secrets
                                    table).
                                  * `managed = false` — secret the user set via the API.
                                    `created_at` / `updated_at` are set; `description` is
                                    null.
                              properties:
                                key:
                                  type: string
                                managed:
                                  type: boolean
                                  description: True for managed system secrets, false for user-set entries.
                                description:
                                  type:
                                    - string
                                    - "null"
                                  description: Set on managed entries only; null on user-set entries.
                                created_at:
                                  type:
                                    - string
                                    - "null"
                                  format: date-time
                                  description: Set on user-set entries only; null on managed entries.
                                updated_at:
                                  type:
                                    - string
                                    - "null"
                                  format: date-time
                                  description: Set on user-set entries only; null on managed entries.
                              required:
                                - key
                                - managed
                        required:
                          - items
          headers:
            ratelimit-limit:
              description: Maximum number of requests allowed in the current window.
              schema:
                type: integer
                minimum: 1
                example: 120
            ratelimit-remaining:
              description: Remaining requests in the current window.
              schema:
                type: integer
                minimum: 0
                example: 118
            ratelimit-reset:
              description: Unix timestamp (seconds) when the current window resets.
              schema:
                type: integer
                example: 1700000060
            ratelimit-policy:
              description: Rate-limit policy in `limit;w=seconds` format, e.g. `120;w=60`.
              schema:
                type: string
                example: 120;w=60
        "401":
          $ref: "#/components/responses/Unauthorized"
          description: Invalid or missing API key
        "404":
          $ref: "#/components/responses/NotFound"
          description: Resource not found
      security:
        - BearerAuth: []
    post:
      operationId: createFunctionSecret
      summary: Create or update a secret
      description: |
        Idempotent insert-or-update keyed on `(function_id, key)`.
        Returns 201 the first time the key is set, 200 on subsequent
        updates. Values are encrypted at rest and only become visible
        to the running handler on the next deploy (`PUT /functions/{id}`
        with the existing code is sufficient to refresh bindings).

        Keys must match `^[A-Z_][A-Z0-9_]*$` (uppercase letters,
        digits, underscores; first character is a letter or
        underscore). Values are at most 4096 UTF-8 bytes. System-
        managed keys are reserved and rejected.
      tags:
        - Functions
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              additionalProperties: false
              description: Body for POST /functions/{id}/secrets.
              properties:
                key:
                  type: string
                  pattern: ^[A-Z_][A-Z0-9_]*$
                  description: |
                    Uppercase letters, digits, and underscores. Must start with
                    a letter or underscore. System-managed keys (e.g.
                    PRIMITIVE_WEBHOOK_SECRET, PRIMITIVE_API_KEY, and
                    PRIMITIVE_API_BASE_URL) are reserved.
                value:
                  type: string
                  minLength: 1
                  maxLength: 4096
                  description: |
                    Secret value, up to 4096 UTF-8 bytes. Encrypted at rest.
                    Never returned by any read endpoint.
              required:
                - key
                - value
      responses:
        "200":
          description: Secret updated
          content:
            application/json:
              schema:
                allOf:
                  - type: object
                    properties:
                      success:
                        type: boolean
                        const: true
                    required:
                      - success
                      - data
                  - type: object
                    properties:
                      data:
                        type: object
                        description: Returned by POST and PUT secret routes.
                        properties:
                          key:
                            type: string
                          created_at:
                            type: string
                            format: date-time
                          updated_at:
                            type: string
                            format: date-time
                          created:
                            type: boolean
                            description: True if this call inserted a new row, false if it updated an existing one.
                        required:
                          - key
                          - created_at
                          - updated_at
                          - created
          headers:
            ratelimit-limit:
              description: Maximum number of requests allowed in the current window.
              schema:
                type: integer
                minimum: 1
                example: 120
            ratelimit-remaining:
              description: Remaining requests in the current window.
              schema:
                type: integer
                minimum: 0
                example: 118
            ratelimit-reset:
              description: Unix timestamp (seconds) when the current window resets.
              schema:
                type: integer
                example: 1700000060
            ratelimit-policy:
              description: Rate-limit policy in `limit;w=seconds` format, e.g. `120;w=60`.
              schema:
                type: string
                example: 120;w=60
        "201":
          description: Secret created
          content:
            application/json:
              schema:
                allOf:
                  - type: object
                    properties:
                      success:
                        type: boolean
                        const: true
                    required:
                      - success
                      - data
                  - type: object
                    properties:
                      data:
                        type: object
                        description: Returned by POST and PUT secret routes.
                        properties:
                          key:
                            type: string
                          created_at:
                            type: string
                            format: date-time
                          updated_at:
                            type: string
                            format: date-time
                          created:
                            type: boolean
                            description: True if this call inserted a new row, false if it updated an existing one.
                        required:
                          - key
                          - created_at
                          - updated_at
                          - created
        "400":
          $ref: "#/components/responses/ValidationError"
          description: Invalid request parameters
        "401":
          $ref: "#/components/responses/Unauthorized"
          description: Invalid or missing API key
        "404":
          $ref: "#/components/responses/NotFound"
          description: Resource not found
      security:
        - BearerAuth: []
      parameters:
        - name: Idempotency-Key
          in: header
          required: false
          description: Optional client-supplied idempotency key. Retrying a request with the same key returns the original result instead of performing the action a second time; if omitted the server derives one from the canonical payload hash. Safe to retry network failures without duplicating side effects.
          schema:
            type: string
            minLength: 1
            maxLength: 255
  /functions/{id}/secrets/{key}:
    parameters:
      - name: id
        in: path
        required: true
        schema:
          type: string
          format: uuid
        description: Resource UUID
      - name: key
        in: path
        required: true
        description: Secret key. Must match `^[A-Z_][A-Z0-9_]*$`.
        schema:
          type: string
          pattern: ^[A-Z_][A-Z0-9_]*$
    put:
      operationId: setFunctionSecret
      summary: Set a secret by key
      description: |
        Path-keyed companion to `POST /functions/{id}/secrets`.
        Idempotent: returns 201 the first time the key is set, 200 on
        subsequent updates. Same validation rules and same write-only
        guarantees as the POST verb; the new value lands in the running
        handler on the next deploy.
      tags:
        - Functions
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              additionalProperties: false
              description: Body for PUT /functions/{id}/secrets/{key}. Key comes from the path.
              properties:
                value:
                  type: string
                  minLength: 1
                  maxLength: 4096
              required:
                - value
      responses:
        "200":
          description: Secret updated
          content:
            application/json:
              schema:
                allOf:
                  - type: object
                    properties:
                      success:
                        type: boolean
                        const: true
                    required:
                      - success
                      - data
                  - type: object
                    properties:
                      data:
                        type: object
                        description: Returned by POST and PUT secret routes.
                        properties:
                          key:
                            type: string
                          created_at:
                            type: string
                            format: date-time
                          updated_at:
                            type: string
                            format: date-time
                          created:
                            type: boolean
                            description: True if this call inserted a new row, false if it updated an existing one.
                        required:
                          - key
                          - created_at
                          - updated_at
                          - created
          headers:
            ratelimit-limit:
              description: Maximum number of requests allowed in the current window.
              schema:
                type: integer
                minimum: 1
                example: 120
            ratelimit-remaining:
              description: Remaining requests in the current window.
              schema:
                type: integer
                minimum: 0
                example: 118
            ratelimit-reset:
              description: Unix timestamp (seconds) when the current window resets.
              schema:
                type: integer
                example: 1700000060
            ratelimit-policy:
              description: Rate-limit policy in `limit;w=seconds` format, e.g. `120;w=60`.
              schema:
                type: string
                example: 120;w=60
        "201":
          description: Secret created
          content:
            application/json:
              schema:
                allOf:
                  - type: object
                    properties:
                      success:
                        type: boolean
                        const: true
                    required:
                      - success
                      - data
                  - type: object
                    properties:
                      data:
                        type: object
                        description: Returned by POST and PUT secret routes.
                        properties:
                          key:
                            type: string
                          created_at:
                            type: string
                            format: date-time
                          updated_at:
                            type: string
                            format: date-time
                          created:
                            type: boolean
                            description: True if this call inserted a new row, false if it updated an existing one.
                        required:
                          - key
                          - created_at
                          - updated_at
                          - created
        "400":
          $ref: "#/components/responses/ValidationError"
          description: Invalid request parameters
        "401":
          $ref: "#/components/responses/Unauthorized"
          description: Invalid or missing API key
        "404":
          $ref: "#/components/responses/NotFound"
          description: Resource not found
      security:
        - BearerAuth: []
    delete:
      operationId: deleteFunctionSecret
      summary: Delete a secret
      description: |
        Removes the secret. The binding stays live in the running
        handler until the next deploy refreshes the binding set
        (`PUT /functions/{id}` with the existing code is sufficient).
        Returns 404 if the key did not exist. Managed system keys
        cannot be deleted.
      tags:
        - Functions
      responses:
        "204":
          description: Secret deleted
        "400":
          $ref: "#/components/responses/ValidationError"
          description: Invalid request parameters
        "401":
          $ref: "#/components/responses/Unauthorized"
          description: Invalid or missing API key
        "404":
          $ref: "#/components/responses/NotFound"
          description: Resource not found
      security:
        - BearerAuth: []
  /org/secrets:
    get:
      operationId: listOrgSecrets
      summary: List org-level (global) secrets
      description: |
        Returns metadata for every org-level secret. Org secrets apply
        to every function in the org and are read as `env.<KEY>` in
        handlers. **Values are never returned.** Secret writes are
        write-only. A function-level secret of the same name overrides
        the org-level value for that function.
      tags:
        - Functions
      responses:
        "200":
          description: List of org secrets (metadata only, no values)
          content:
            application/json:
              schema:
                allOf:
                  - type: object
                    properties:
                      success:
                        type: boolean
                        const: true
                    required:
                      - success
                      - data
                  - type: object
                    properties:
                      data:
                        type: object
                        properties:
                          items:
                            type: array
                            items:
                              type: object
                              description: |
                                One row from GET /org/secrets. Org secrets are always user-set
                                (there are no managed org secrets), so `created_at` /
                                `updated_at` are always present.
                              properties:
                                key:
                                  type: string
                                created_at:
                                  type: string
                                  format: date-time
                                updated_at:
                                  type: string
                                  format: date-time
                              required:
                                - key
                                - created_at
                                - updated_at
                        required:
                          - items
          headers:
            ratelimit-limit:
              description: Maximum number of requests allowed in the current window.
              schema:
                type: integer
                minimum: 1
                example: 120
            ratelimit-remaining:
              description: Remaining requests in the current window.
              schema:
                type: integer
                minimum: 0
                example: 118
            ratelimit-reset:
              description: Unix timestamp (seconds) when the current window resets.
              schema:
                type: integer
                example: 1700000060
            ratelimit-policy:
              description: Rate-limit policy in `limit;w=seconds` format, e.g. `120;w=60`.
              schema:
                type: string
                example: 120;w=60
        "401":
          $ref: "#/components/responses/Unauthorized"
          description: Invalid or missing API key
      security:
        - BearerAuth: []
    post:
      operationId: createOrgSecret
      summary: Create or update an org secret
      description: |
        Idempotent insert-or-update keyed on `(org_id, key)`. Returns
        201 the first time the key is set, 200 on subsequent updates.
        Values are encrypted at rest. A changed value lands in a
        function only on that function's next deploy.

        Keys must match `^[A-Z_][A-Z0-9_]*$` (uppercase letters,
        digits, underscores; first character is a letter or
        underscore). Values are at most 4096 UTF-8 bytes. System-
        managed keys are reserved and rejected.
      tags:
        - Functions
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              additionalProperties: false
              description: Body for POST /org/secrets.
              properties:
                key:
                  type: string
                  pattern: ^[A-Z_][A-Z0-9_]*$
                  description: |
                    Uppercase letters, digits, and underscores. Must start with
                    a letter or underscore. System-managed keys are reserved.
                value:
                  type: string
                  minLength: 1
                  maxLength: 4096
                  description: |
                    Secret value, up to 4096 UTF-8 bytes. Encrypted at rest.
                    Never returned by any read endpoint.
              required:
                - key
                - value
      responses:
        "200":
          description: Secret updated
          content:
            application/json:
              schema:
                allOf:
                  - type: object
                    properties:
                      success:
                        type: boolean
                        const: true
                    required:
                      - success
                      - data
                  - type: object
                    properties:
                      data:
                        type: object
                        description: Returned by POST and PUT org secret routes.
                        properties:
                          key:
                            type: string
                          created_at:
                            type: string
                            format: date-time
                          updated_at:
                            type: string
                            format: date-time
                          created:
                            type: boolean
                            description: True if this call inserted a new row, false if it updated an existing one.
                        required:
                          - key
                          - created_at
                          - updated_at
                          - created
          headers:
            ratelimit-limit:
              description: Maximum number of requests allowed in the current window.
              schema:
                type: integer
                minimum: 1
                example: 120
            ratelimit-remaining:
              description: Remaining requests in the current window.
              schema:
                type: integer
                minimum: 0
                example: 118
            ratelimit-reset:
              description: Unix timestamp (seconds) when the current window resets.
              schema:
                type: integer
                example: 1700000060
            ratelimit-policy:
              description: Rate-limit policy in `limit;w=seconds` format, e.g. `120;w=60`.
              schema:
                type: string
                example: 120;w=60
        "201":
          description: Secret created
          content:
            application/json:
              schema:
                allOf:
                  - type: object
                    properties:
                      success:
                        type: boolean
                        const: true
                    required:
                      - success
                      - data
                  - type: object
                    properties:
                      data:
                        type: object
                        description: Returned by POST and PUT org secret routes.
                        properties:
                          key:
                            type: string
                          created_at:
                            type: string
                            format: date-time
                          updated_at:
                            type: string
                            format: date-time
                          created:
                            type: boolean
                            description: True if this call inserted a new row, false if it updated an existing one.
                        required:
                          - key
                          - created_at
                          - updated_at
                          - created
        "400":
          $ref: "#/components/responses/ValidationError"
          description: Invalid request parameters
        "401":
          $ref: "#/components/responses/Unauthorized"
          description: Invalid or missing API key
      security:
        - BearerAuth: []
      parameters:
        - name: Idempotency-Key
          in: header
          required: false
          description: Optional client-supplied idempotency key. Retrying a request with the same key returns the original result instead of performing the action a second time; if omitted the server derives one from the canonical payload hash. Safe to retry network failures without duplicating side effects.
          schema:
            type: string
            minLength: 1
            maxLength: 255
  /org/secrets/{key}:
    parameters:
      - name: key
        in: path
        required: true
        description: Secret key. Must match `^[A-Z_][A-Z0-9_]*$`.
        schema:
          type: string
          pattern: ^[A-Z_][A-Z0-9_]*$
    put:
      operationId: setOrgSecret
      summary: Set an org secret by key
      description: |
        Path-keyed companion to `POST /org/secrets`. Idempotent:
        returns 201 the first time the key is set, 200 on subsequent
        updates. Same validation and write-only guarantees as POST.
      tags:
        - Functions
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              additionalProperties: false
              description: Body for PUT /org/secrets/{key}. Key comes from the path.
              properties:
                value:
                  type: string
                  minLength: 1
                  maxLength: 4096
              required:
                - value
      responses:
        "200":
          description: Secret updated
          content:
            application/json:
              schema:
                allOf:
                  - type: object
                    properties:
                      success:
                        type: boolean
                        const: true
                    required:
                      - success
                      - data
                  - type: object
                    properties:
                      data:
                        type: object
                        description: Returned by POST and PUT org secret routes.
                        properties:
                          key:
                            type: string
                          created_at:
                            type: string
                            format: date-time
                          updated_at:
                            type: string
                            format: date-time
                          created:
                            type: boolean
                            description: True if this call inserted a new row, false if it updated an existing one.
                        required:
                          - key
                          - created_at
                          - updated_at
                          - created
          headers:
            ratelimit-limit:
              description: Maximum number of requests allowed in the current window.
              schema:
                type: integer
                minimum: 1
                example: 120
            ratelimit-remaining:
              description: Remaining requests in the current window.
              schema:
                type: integer
                minimum: 0
                example: 118
            ratelimit-reset:
              description: Unix timestamp (seconds) when the current window resets.
              schema:
                type: integer
                example: 1700000060
            ratelimit-policy:
              description: Rate-limit policy in `limit;w=seconds` format, e.g. `120;w=60`.
              schema:
                type: string
                example: 120;w=60
        "201":
          description: Secret created
          content:
            application/json:
              schema:
                allOf:
                  - type: object
                    properties:
                      success:
                        type: boolean
                        const: true
                    required:
                      - success
                      - data
                  - type: object
                    properties:
                      data:
                        type: object
                        description: Returned by POST and PUT org secret routes.
                        properties:
                          key:
                            type: string
                          created_at:
                            type: string
                            format: date-time
                          updated_at:
                            type: string
                            format: date-time
                          created:
                            type: boolean
                            description: True if this call inserted a new row, false if it updated an existing one.
                        required:
                          - key
                          - created_at
                          - updated_at
                          - created
        "400":
          $ref: "#/components/responses/ValidationError"
          description: Invalid request parameters
        "401":
          $ref: "#/components/responses/Unauthorized"
          description: Invalid or missing API key
      security:
        - BearerAuth: []
    delete:
      operationId: deleteOrgSecret
      summary: Delete an org secret
      description: |
        Removes the org secret. Functions keep the previous value until
        each is redeployed. Returns 404 if the key did not exist.
      tags:
        - Functions
      responses:
        "204":
          description: Secret deleted
        "400":
          $ref: "#/components/responses/ValidationError"
          description: Invalid request parameters
        "401":
          $ref: "#/components/responses/Unauthorized"
          description: Invalid or missing API key
        "404":
          $ref: "#/components/responses/NotFound"
          description: Resource not found
      security:
        - BearerAuth: []
  /functions/{id}/logs:
    parameters:
      - name: id
        in: path
        required: true
        schema:
          type: string
          format: uuid
        description: Resource UUID
    get:
      operationId: listFunctionLogs
      summary: List a function's execution logs
      description: |
        Returns the most recent `function_logs` rows for the function,
        newest first. Each row is a single `console.log` / `console.error`
        invocation captured from the running handler.

        Page through history with the opaque `cursor` returned as
        `next_cursor`; pass it back as the `cursor` query param on the
        next call. `next_cursor` is `null` when there are no further
        rows. The cursor format is an implementation detail and should
        not be parsed by callers.
      tags:
        - Functions
      parameters:
        - name: limit
          in: query
          required: false
          description: |
            Maximum number of rows to return. Clamped to 1..200; default
            50.
          schema:
            type: integer
            minimum: 1
            maximum: 200
            default: 50
        - name: cursor
          in: query
          required: false
          description: |
            Opaque pagination cursor from a previous response's
            `next_cursor`. Omit on the first call.
          schema:
            type: string
      responses:
        "200":
          description: List of log rows (newest first) plus pagination cursor.
          content:
            application/json:
              schema:
                allOf:
                  - type: object
                    properties:
                      success:
                        type: boolean
                        const: true
                    required:
                      - success
                      - data
                  - type: object
                    properties:
                      data:
                        type: object
                        properties:
                          items:
                            type: array
                            items:
                              type: object
                              description: |
                                One row from GET /functions/{id}/logs. Represents a single
                                captured log line emitted by the running handler (e.g. via
                                `console.log` / `console.error`).
                              properties:
                                id:
                                  type: string
                                  format: uuid
                                  description: Unique log row id (stable across pages).
                                function_id:
                                  type: string
                                  format: uuid
                                  description: The function this log row belongs to.
                                level:
                                  type: string
                                  enum:
                                    - debug
                                    - log
                                    - info
                                    - warn
                                    - error
                                  description: |
                                    Severity. `log` is the runtime's default for unannotated
                                    `console.log` calls; the other levels match standard
                                    `console.*` methods.
                                message:
                                  type: string
                                  description: |
                                    The textual message body. The runtime stringifies non-string
                                    arguments before persisting, so this is always a plain
                                    string.
                                ts:
                                  type: string
                                  format: date-time
                                  description: |
                                    When the handler emitted this line. Newest-first ordering
                                    on this column drives pagination; clock is the runtime's,
                                    not the gateway's.
                                metadata:
                                  type:
                                    - object
                                    - "null"
                                  additionalProperties: true
                                  description: |
                                    Optional structured payload the runtime attaches alongside
                                    the message (e.g. extra args passed to `console.log`).
                                    Shape is opaque; treat keys as untyped.
                              required:
                                - id
                                - function_id
                                - level
                                - message
                                - ts
                          next_cursor:
                            type:
                              - string
                              - "null"
                            description: |
                              Pass back as `cursor` to fetch the next
                              page. `null` when no further rows exist.
                        required:
                          - items
                          - next_cursor
          headers:
            ratelimit-limit:
              description: Maximum number of requests allowed in the current window.
              schema:
                type: integer
                minimum: 1
                example: 120
            ratelimit-remaining:
              description: Remaining requests in the current window.
              schema:
                type: integer
                minimum: 0
                example: 118
            ratelimit-reset:
              description: Unix timestamp (seconds) when the current window resets.
              schema:
                type: integer
                example: 1700000060
            ratelimit-policy:
              description: Rate-limit policy in `limit;w=seconds` format, e.g. `120;w=60`.
              schema:
                type: string
                example: 120;w=60
        "400":
          $ref: "#/components/responses/ValidationError"
          description: Invalid request parameters
        "401":
          $ref: "#/components/responses/Unauthorized"
          description: Invalid or missing API key
        "403":
          $ref: "#/components/responses/Forbidden"
          description: Authenticated caller lacks permission for the operation
        "404":
          $ref: "#/components/responses/NotFound"
          description: Resource not found
      security:
        - BearerAuth: []
  /memories:
    put:
      operationId: setMemory
      summary: Set a memory
      description: Create or update a durable JSON memory under an org or function scope. Function-authenticated requests use their own Function id and cannot override it. If no explicit scope is provided for other credentials, `x-primitive-function-id` is used next, and other requests default to org scope. Function scope uses the function id UUID, not the function name.
      tags:
        - Memories
      security:
        - BearerAuth: []
      parameters:
        - name: x-primitive-function-id
          in: header
          required: false
          description: Optional function id UUID used as the default scope for non-function-authenticated requests when the body does not include `scope`. Ignored when `scope` is provided.
          schema:
            type: string
            format: uuid
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              additionalProperties: false
              properties:
                key:
                  type: string
                  minLength: 1
                  maxLength: 512
                  description: Caller-defined key, at most 512 UTF-8 bytes.
                value:
                  description: JSON value accepted by Primitive Memories. The server accepts strings, numbers, booleans, null, arrays, and objects, validates nested values, and rejects values that do not serialize as JSON.
                  oneOf:
                    - type: "null"
                    - type: string
                    - type: number
                    - type: boolean
                    - type: array
                      items:
                        $ref: "#/components/schemas/MemoryJsonValue"
                    - type: object
                      additionalProperties:
                        $ref: "#/components/schemas/MemoryJsonValue"
                scope:
                  description: Memory scope. `org` resolves to the authenticated organization. `function` requires the function id UUID in `id`; function names are not valid scope identifiers. Function-authenticated requests cannot override their own Function scope.
                  oneOf:
                    - type: object
                      additionalProperties: false
                      properties:
                        type:
                          type: string
                          enum:
                            - org
                      required:
                        - type
                    - type: object
                      additionalProperties: false
                      properties:
                        type:
                          type: string
                          enum:
                            - function
                        id:
                          type: string
                          format: uuid
                          description: Function id UUID.
                      required:
                        - type
                        - id
                ttl_seconds:
                  type: integer
                  minimum: 1
                  maximum: 31536000
                  description: Set or replace the TTL in seconds. Mutually exclusive with `expires_at` and `clear_ttl`.
                expires_at:
                  type: string
                  format: date-time
                  description: Set or replace the absolute expiration timestamp. Mutually exclusive with `ttl_seconds` and `clear_ttl`.
                clear_ttl:
                  type: boolean
                  description: Clear any existing TTL. Mutually exclusive with `ttl_seconds` and `expires_at`.
                if_absent:
                  type: boolean
                  description: Create only when the key is absent. Mutually exclusive with `if_version`.
                if_version:
                  type: string
                  pattern: ^[0-9]+$
                  description: Bigint counter serialized as a base-10 string.
              required:
                - key
                - value
      responses:
        "200":
          description: Existing memory updated.
          content:
            application/json:
              schema:
                allOf:
                  - type: object
                    properties:
                      success:
                        type: boolean
                        const: true
                    required:
                      - success
                      - data
                  - type: object
                    properties:
                      data:
                        type: object
                        additionalProperties: false
                        description: Memory record returned by get and set operations.
                        properties:
                          id:
                            type: string
                            format: uuid
                          key:
                            type: string
                            minLength: 1
                            maxLength: 512
                            description: Caller-defined key, at most 512 UTF-8 bytes.
                          scope:
                            type: object
                            additionalProperties: false
                            description: Resolved memory scope returned by the API.
                            properties:
                              type:
                                type: string
                                enum:
                                  - org
                                  - function
                              id:
                                type: string
                                format: uuid
                                description: Org id for org scope, function id for function scope.
                            required:
                              - type
                              - id
                          value:
                            description: JSON value accepted by Primitive Memories. The server accepts strings, numbers, booleans, null, arrays, and objects, validates nested values, and rejects values that do not serialize as JSON.
                            oneOf:
                              - type: "null"
                              - type: string
                              - type: number
                              - type: boolean
                              - type: array
                                items:
                                  $ref: "#/components/schemas/MemoryJsonValue"
                              - type: object
                                additionalProperties:
                                  $ref: "#/components/schemas/MemoryJsonValue"
                          version:
                            type: string
                            pattern: ^[0-9]+$
                            description: Bigint counter serialized as a base-10 string.
                          created_at:
                            type: string
                            format: date-time
                          updated_at:
                            type: string
                            format: date-time
                          last_read_at:
                            type:
                              - string
                              - "null"
                            format: date-time
                            description: Last successful get timestamp, or null before any get.
                          read_count:
                            type: string
                            pattern: ^[0-9]+$
                            description: Bigint counter serialized as a base-10 string.
                          write_count:
                            type: string
                            pattern: ^[0-9]+$
                            description: Bigint counter serialized as a base-10 string.
                          expires_at:
                            type:
                              - string
                              - "null"
                            format: date-time
                            description: Expiration timestamp, or null for no TTL.
                          created_by:
                            type:
                              - string
                              - "null"
                            description: Actor that created the memory, when available.
                          updated_by:
                            type:
                              - string
                              - "null"
                            description: Actor that last updated the memory, when available.
                        required:
                          - id
                          - key
                          - scope
                          - value
                          - version
                          - created_at
                          - updated_at
                          - last_read_at
                          - read_count
                          - write_count
                          - expires_at
                          - created_by
                          - updated_by
          headers:
            ratelimit-limit:
              description: Maximum number of requests allowed in the current window.
              schema:
                type: integer
                minimum: 1
                example: 120
            ratelimit-remaining:
              description: Remaining requests in the current window.
              schema:
                type: integer
                minimum: 0
                example: 118
            ratelimit-reset:
              description: Unix timestamp (seconds) when the current window resets.
              schema:
                type: integer
                example: 1700000060
            ratelimit-policy:
              description: Rate-limit policy in `limit;w=seconds` format, e.g. `120;w=60`.
              schema:
                type: string
                example: 120;w=60
        "201":
          description: Memory created.
          content:
            application/json:
              schema:
                allOf:
                  - type: object
                    properties:
                      success:
                        type: boolean
                        const: true
                    required:
                      - success
                      - data
                  - type: object
                    properties:
                      data:
                        type: object
                        additionalProperties: false
                        description: Memory record returned by get and set operations.
                        properties:
                          id:
                            type: string
                            format: uuid
                          key:
                            type: string
                            minLength: 1
                            maxLength: 512
                            description: Caller-defined key, at most 512 UTF-8 bytes.
                          scope:
                            type: object
                            additionalProperties: false
                            description: Resolved memory scope returned by the API.
                            properties:
                              type:
                                type: string
                                enum:
                                  - org
                                  - function
                              id:
                                type: string
                                format: uuid
                                description: Org id for org scope, function id for function scope.
                            required:
                              - type
                              - id
                          value:
                            description: JSON value accepted by Primitive Memories. The server accepts strings, numbers, booleans, null, arrays, and objects, validates nested values, and rejects values that do not serialize as JSON.
                            oneOf:
                              - type: "null"
                              - type: string
                              - type: number
                              - type: boolean
                              - type: array
                                items:
                                  $ref: "#/components/schemas/MemoryJsonValue"
                              - type: object
                                additionalProperties:
                                  $ref: "#/components/schemas/MemoryJsonValue"
                          version:
                            type: string
                            pattern: ^[0-9]+$
                            description: Bigint counter serialized as a base-10 string.
                          created_at:
                            type: string
                            format: date-time
                          updated_at:
                            type: string
                            format: date-time
                          last_read_at:
                            type:
                              - string
                              - "null"
                            format: date-time
                            description: Last successful get timestamp, or null before any get.
                          read_count:
                            type: string
                            pattern: ^[0-9]+$
                            description: Bigint counter serialized as a base-10 string.
                          write_count:
                            type: string
                            pattern: ^[0-9]+$
                            description: Bigint counter serialized as a base-10 string.
                          expires_at:
                            type:
                              - string
                              - "null"
                            format: date-time
                            description: Expiration timestamp, or null for no TTL.
                          created_by:
                            type:
                              - string
                              - "null"
                            description: Actor that created the memory, when available.
                          updated_by:
                            type:
                              - string
                              - "null"
                            description: Actor that last updated the memory, when available.
                        required:
                          - id
                          - key
                          - scope
                          - value
                          - version
                          - created_at
                          - updated_at
                          - last_read_at
                          - read_count
                          - write_count
                          - expires_at
                          - created_by
                          - updated_by
        "400":
          $ref: "#/components/responses/ValidationError"
          description: Invalid request parameters
        "401":
          $ref: "#/components/responses/Unauthorized"
          description: Invalid or missing API key
        "402":
          $ref: "#/components/responses/PaymentRequired"
          description: Usage credits are exhausted or payment is required.
        "403":
          $ref: "#/components/responses/Forbidden"
          description: Authenticated caller lacks permission for the operation
        "404":
          $ref: "#/components/responses/NotFound"
          description: Resource not found
        "409":
          $ref: "#/components/responses/Conflict"
          description: The request conflicts with the current state of the resource
        "429":
          $ref: "#/components/responses/RateLimited"
          description: Rate limit exceeded
    get:
      operationId: getMemory
      summary: Get a memory
      description: Fetch one active memory by key and scope. Omit scope parameters to use automatic scope resolution. Function-authenticated requests use their own Function id and cannot override it. Function scope uses a function id UUID in `scope_id`.
      tags:
        - Memories
      security:
        - BearerAuth: []
      parameters:
        - name: key
          in: query
          required: true
          description: Memory key. Must be at most 512 UTF-8 bytes.
          schema:
            type: string
            minLength: 1
            maxLength: 512
        - name: scope_type
          in: query
          required: false
          description: Explicit scope type. Omit to use automatic scope resolution. Pass `function` with `scope_id=<function-id>`, or `org` with no `scope_id`. Function-authenticated requests cannot override their automatic Function scope.
          schema:
            type: string
            enum:
              - org
              - function
        - name: scope_id
          in: query
          required: false
          description: Function id UUID when `scope_type=function`. Not valid with `scope_type=org`. Function-authenticated requests may only use their own Function id.
          schema:
            type: string
            format: uuid
        - name: x-primitive-function-id
          in: header
          required: false
          description: Optional function id UUID used as the default scope for non-function-authenticated requests when query scope parameters are omitted.
          schema:
            type: string
            format: uuid
      responses:
        "200":
          description: Memory record with value.
          content:
            application/json:
              schema:
                allOf:
                  - type: object
                    properties:
                      success:
                        type: boolean
                        const: true
                    required:
                      - success
                      - data
                  - type: object
                    properties:
                      data:
                        type: object
                        additionalProperties: false
                        description: Memory record returned by get and set operations.
                        properties:
                          id:
                            type: string
                            format: uuid
                          key:
                            type: string
                            minLength: 1
                            maxLength: 512
                            description: Caller-defined key, at most 512 UTF-8 bytes.
                          scope:
                            type: object
                            additionalProperties: false
                            description: Resolved memory scope returned by the API.
                            properties:
                              type:
                                type: string
                                enum:
                                  - org
                                  - function
                              id:
                                type: string
                                format: uuid
                                description: Org id for org scope, function id for function scope.
                            required:
                              - type
                              - id
                          value:
                            description: JSON value accepted by Primitive Memories. The server accepts strings, numbers, booleans, null, arrays, and objects, validates nested values, and rejects values that do not serialize as JSON.
                            oneOf:
                              - type: "null"
                              - type: string
                              - type: number
                              - type: boolean
                              - type: array
                                items:
                                  $ref: "#/components/schemas/MemoryJsonValue"
                              - type: object
                                additionalProperties:
                                  $ref: "#/components/schemas/MemoryJsonValue"
                          version:
                            type: string
                            pattern: ^[0-9]+$
                            description: Bigint counter serialized as a base-10 string.
                          created_at:
                            type: string
                            format: date-time
                          updated_at:
                            type: string
                            format: date-time
                          last_read_at:
                            type:
                              - string
                              - "null"
                            format: date-time
                            description: Last successful get timestamp, or null before any get.
                          read_count:
                            type: string
                            pattern: ^[0-9]+$
                            description: Bigint counter serialized as a base-10 string.
                          write_count:
                            type: string
                            pattern: ^[0-9]+$
                            description: Bigint counter serialized as a base-10 string.
                          expires_at:
                            type:
                              - string
                              - "null"
                            format: date-time
                            description: Expiration timestamp, or null for no TTL.
                          created_by:
                            type:
                              - string
                              - "null"
                            description: Actor that created the memory, when available.
                          updated_by:
                            type:
                              - string
                              - "null"
                            description: Actor that last updated the memory, when available.
                        required:
                          - id
                          - key
                          - scope
                          - value
                          - version
                          - created_at
                          - updated_at
                          - last_read_at
                          - read_count
                          - write_count
                          - expires_at
                          - created_by
                          - updated_by
          headers:
            ratelimit-limit:
              description: Maximum number of requests allowed in the current window.
              schema:
                type: integer
                minimum: 1
                example: 120
            ratelimit-remaining:
              description: Remaining requests in the current window.
              schema:
                type: integer
                minimum: 0
                example: 118
            ratelimit-reset:
              description: Unix timestamp (seconds) when the current window resets.
              schema:
                type: integer
                example: 1700000060
            ratelimit-policy:
              description: Rate-limit policy in `limit;w=seconds` format, e.g. `120;w=60`.
              schema:
                type: string
                example: 120;w=60
        "400":
          $ref: "#/components/responses/ValidationError"
          description: Invalid request parameters
        "401":
          $ref: "#/components/responses/Unauthorized"
          description: Invalid or missing API key
        "402":
          $ref: "#/components/responses/PaymentRequired"
          description: Usage credits are exhausted or payment is required.
        "403":
          $ref: "#/components/responses/Forbidden"
          description: Authenticated caller lacks permission for the operation
        "404":
          $ref: "#/components/responses/NotFound"
          description: Resource not found
        "429":
          $ref: "#/components/responses/RateLimited"
          description: Rate limit exceeded
    delete:
      operationId: deleteMemory
      summary: Delete a memory
      description: Delete one active memory by key and scope. Function-authenticated requests use their own Function id and cannot override it. Deletes are idempotent without `if_version`; a stale `if_version` returns `memory_conflict`.
      tags:
        - Memories
      security:
        - BearerAuth: []
      parameters:
        - name: key
          in: query
          required: true
          description: Memory key. Must be at most 512 UTF-8 bytes.
          schema:
            type: string
            minLength: 1
            maxLength: 512
        - name: scope_type
          in: query
          required: false
          description: Explicit scope type. Omit to use automatic scope resolution. Pass `function` with `scope_id=<function-id>`, or `org` with no `scope_id`. Function-authenticated requests cannot override their automatic Function scope.
          schema:
            type: string
            enum:
              - org
              - function
        - name: scope_id
          in: query
          required: false
          description: Function id UUID when `scope_type=function`. Not valid with `scope_type=org`. Function-authenticated requests may only use their own Function id.
          schema:
            type: string
            format: uuid
        - name: if_version
          in: query
          required: false
          description: Optional compare-and-delete version.
          schema:
            type: string
            pattern: ^[0-9]+$
            description: Bigint counter serialized as a base-10 string.
        - name: x-primitive-function-id
          in: header
          required: false
          description: Optional function id UUID used as the default scope for non-function-authenticated requests when query scope parameters are omitted.
          schema:
            type: string
            format: uuid
      responses:
        "200":
          description: Delete result.
          content:
            application/json:
              schema:
                allOf:
                  - type: object
                    properties:
                      success:
                        type: boolean
                        const: true
                    required:
                      - success
                      - data
                  - type: object
                    properties:
                      data:
                        type: object
                        additionalProperties: false
                        properties:
                          deleted:
                            type: boolean
                          key:
                            type: string
                          scope:
                            type: object
                            additionalProperties: false
                            description: Resolved memory scope returned by the API.
                            properties:
                              type:
                                type: string
                                enum:
                                  - org
                                  - function
                              id:
                                type: string
                                format: uuid
                                description: Org id for org scope, function id for function scope.
                            required:
                              - type
                              - id
                        required:
                          - deleted
                          - key
                          - scope
          headers:
            ratelimit-limit:
              description: Maximum number of requests allowed in the current window.
              schema:
                type: integer
                minimum: 1
                example: 120
            ratelimit-remaining:
              description: Remaining requests in the current window.
              schema:
                type: integer
                minimum: 0
                example: 118
            ratelimit-reset:
              description: Unix timestamp (seconds) when the current window resets.
              schema:
                type: integer
                example: 1700000060
            ratelimit-policy:
              description: Rate-limit policy in `limit;w=seconds` format, e.g. `120;w=60`.
              schema:
                type: string
                example: 120;w=60
        "400":
          $ref: "#/components/responses/ValidationError"
          description: Invalid request parameters
        "401":
          $ref: "#/components/responses/Unauthorized"
          description: Invalid or missing API key
        "402":
          $ref: "#/components/responses/PaymentRequired"
          description: Usage credits are exhausted or payment is required.
        "403":
          $ref: "#/components/responses/Forbidden"
          description: Authenticated caller lacks permission for the operation
        "404":
          $ref: "#/components/responses/NotFound"
          description: Resource not found
        "409":
          $ref: "#/components/responses/Conflict"
          description: The request conflicts with the current state of the resource
        "429":
          $ref: "#/components/responses/RateLimited"
          description: Rate limit exceeded
  /memories/search:
    get:
      operationId: searchMemories
      summary: Search memories
      description: List active memories in a scope by lexicographic key prefix. Results are ordered by key ascending. This is prefix search, not free-text search. Function-authenticated requests use their own Function id and cannot override it. Pass `include_value=false` to return metadata only.
      tags:
        - Memories
      security:
        - BearerAuth: []
      parameters:
        - name: prefix
          in: query
          required: false
          description: Key prefix to match. Empty string lists all active memories in the selected scope.
          schema:
            type: string
            maxLength: 512
            default: ""
        - name: cursor
          in: query
          required: false
          description: Key cursor from a previous response meta.cursor.
          schema:
            type: string
            minLength: 1
            maxLength: 512
        - name: limit
          in: query
          schema:
            type: integer
            minimum: 1
            maximum: 100
            default: 50
          description: Number of results per page
        - name: include_value
          in: query
          required: false
          description: Pass `false` to omit the `value` field and return metadata only.
          schema:
            type: string
            enum:
              - "true"
              - "false"
            default: "true"
        - name: updated_after
          in: query
          required: false
          description: Only include memories updated at or after this timestamp.
          schema:
            type: string
            format: date-time
        - name: updated_before
          in: query
          required: false
          description: Only include memories updated at or before this timestamp.
          schema:
            type: string
            format: date-time
        - name: scope_type
          in: query
          required: false
          description: Explicit scope type. Omit to use automatic scope resolution. Pass `function` with `scope_id=<function-id>`, or `org` with no `scope_id`. Function-authenticated requests cannot override their automatic Function scope.
          schema:
            type: string
            enum:
              - org
              - function
        - name: scope_id
          in: query
          required: false
          description: Function id UUID when `scope_type=function`. Not valid with `scope_type=org`. Function-authenticated requests may only use their own Function id.
          schema:
            type: string
            format: uuid
        - name: x-primitive-function-id
          in: header
          required: false
          description: Optional function id UUID used as the default scope for non-function-authenticated requests when query scope parameters are omitted.
          schema:
            type: string
            format: uuid
      responses:
        "200":
          description: Paginated memory records.
          content:
            application/json:
              schema:
                allOf:
                  - type: object
                    properties:
                      success:
                        type: boolean
                        const: true
                      meta:
                        type: object
                        properties:
                          total:
                            type: integer
                            description: Total number of matching records
                          limit:
                            type: integer
                            description: Page size used for this request
                          cursor:
                            type:
                              - string
                              - "null"
                            description: Cursor for the next page, or null if no more results
                        required:
                          - total
                          - limit
                          - cursor
                    required:
                      - success
                      - data
                      - meta
                  - type: object
                    properties:
                      data:
                        type: array
                        items:
                          type: object
                          additionalProperties: false
                          description: Metadata for a Primitive memory. Search responses omit `value` when `include_value=false`.
                          properties:
                            id:
                              type: string
                              format: uuid
                            key:
                              type: string
                              minLength: 1
                              maxLength: 512
                              description: Caller-defined key, at most 512 UTF-8 bytes.
                            scope:
                              type: object
                              additionalProperties: false
                              description: Resolved memory scope returned by the API.
                              properties:
                                type:
                                  type: string
                                  enum:
                                    - org
                                    - function
                                id:
                                  type: string
                                  format: uuid
                                  description: Org id for org scope, function id for function scope.
                              required:
                                - type
                                - id
                            value:
                              description: JSON value accepted by Primitive Memories. The server accepts strings, numbers, booleans, null, arrays, and objects, validates nested values, and rejects values that do not serialize as JSON.
                              oneOf:
                                - type: "null"
                                - type: string
                                - type: number
                                - type: boolean
                                - type: array
                                  items:
                                    $ref: "#/components/schemas/MemoryJsonValue"
                                - type: object
                                  additionalProperties:
                                    $ref: "#/components/schemas/MemoryJsonValue"
                            version:
                              type: string
                              pattern: ^[0-9]+$
                              description: Bigint counter serialized as a base-10 string.
                            created_at:
                              type: string
                              format: date-time
                            updated_at:
                              type: string
                              format: date-time
                            last_read_at:
                              type:
                                - string
                                - "null"
                              format: date-time
                              description: Last successful get timestamp, or null before any get.
                            read_count:
                              type: string
                              pattern: ^[0-9]+$
                              description: Bigint counter serialized as a base-10 string.
                            write_count:
                              type: string
                              pattern: ^[0-9]+$
                              description: Bigint counter serialized as a base-10 string.
                            expires_at:
                              type:
                                - string
                                - "null"
                              format: date-time
                              description: Expiration timestamp, or null for no TTL.
                            created_by:
                              type:
                                - string
                                - "null"
                              description: Actor that created the memory, when available.
                            updated_by:
                              type:
                                - string
                                - "null"
                              description: Actor that last updated the memory, when available.
                          required:
                            - id
                            - key
                            - scope
                            - version
                            - created_at
                            - updated_at
                            - last_read_at
                            - read_count
                            - write_count
                            - expires_at
                            - created_by
                            - updated_by
          headers:
            ratelimit-limit:
              description: Maximum number of requests allowed in the current window.
              schema:
                type: integer
                minimum: 1
                example: 120
            ratelimit-remaining:
              description: Remaining requests in the current window.
              schema:
                type: integer
                minimum: 0
                example: 118
            ratelimit-reset:
              description: Unix timestamp (seconds) when the current window resets.
              schema:
                type: integer
                example: 1700000060
            ratelimit-policy:
              description: Rate-limit policy in `limit;w=seconds` format, e.g. `120;w=60`.
              schema:
                type: string
                example: 120;w=60
        "400":
          $ref: "#/components/responses/ValidationError"
          description: Invalid request parameters
        "401":
          $ref: "#/components/responses/Unauthorized"
          description: Invalid or missing API key
        "402":
          $ref: "#/components/responses/PaymentRequired"
          description: Usage credits are exhausted or payment is required.
        "403":
          $ref: "#/components/responses/Forbidden"
          description: Authenticated caller lacks permission for the operation
        "404":
          $ref: "#/components/responses/NotFound"
          description: Resource not found
        "429":
          $ref: "#/components/responses/RateLimited"
          description: Rate limit exceeded
  /x402/payout-addresses:
    post:
      operationId: registerPayoutAddress
      summary: Register a payout address
      description: |
        Register (or update) the default payout address your org receives x402
        payments at, for a given network. You prove control of the address with
        an org-bound `personal_sign` signature over the message produced by the
        SDK helper `buildPayoutRegistrationMessage`. The org id is taken from your
        authenticated key, never the body, so a captured signature can't register
        an address under another org. Exactly one default address exists per
        (org, network); registering again replaces it. A payee MUST register a
        payout address before calling `createChallenge`, because the challenge's
        `pay_to` is resolved from this directory.
      tags:
        - Payments
      security:
        - BearerAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              additionalProperties: false
              properties:
                address:
                  type: string
                  pattern: ^0x[0-9a-fA-F]{40}$
                  description: The payout address (your signer's own EVM address), 0x-prefixed.
                network:
                  type: string
                  enum:
                    - base
                    - base-sepolia
                  description: The chain the address receives on.
                signature:
                  type: string
                  pattern: ^0x[0-9a-fA-F]+$
                  description: |
                    A `personal_sign` signature over the org-bound message produced by
                    the SDK helper `buildPayoutRegistrationMessage`. Recovered and
                    checked against `address`; the org id is bound into the signed bytes.
                issued_at:
                  type: string
                  format: date-time
                  description: |
                    ISO-8601 timestamp embedded in the signed message. Must be within a
                    short freshness window (about 10 minutes) of server time.
                label:
                  type: string
                  maxLength: 80
                  description: Optional human-readable label.
              required:
                - address
                - network
                - signature
                - issued_at
      responses:
        "201":
          description: Payout address registered (or updated) and set as default
          content:
            application/json:
              schema:
                allOf:
                  - type: object
                    properties:
                      success:
                        type: boolean
                        const: true
                    required:
                      - success
                      - data
                  - type: object
                    properties:
                      data:
                        type: object
                        properties:
                          id:
                            type: string
                            format: uuid
                          address:
                            type: string
                            description: The checksummed payout address.
                          network:
                            type: string
                            enum:
                              - base
                              - base-sepolia
                          label:
                            type:
                              - string
                              - "null"
                          is_default:
                            type: boolean
                            description: Exactly one address per (org, network) is the default.
                          verified_at:
                            type: string
                            format: date-time
                            description: When ownership of the address was last proven.
                          created_at:
                            type: string
                            format: date-time
                        required:
                          - id
                          - address
                          - network
                          - label
                          - is_default
                          - verified_at
        "400":
          $ref: "#/components/responses/ValidationError"
          description: Invalid request parameters
        "401":
          $ref: "#/components/responses/Unauthorized"
          description: Invalid or missing API key
        "403":
          $ref: "#/components/responses/Forbidden"
          description: Authenticated caller lacks permission for the operation
        "422":
          $ref: "#/components/responses/UnprocessableEntity"
          description: |
            The request was well-formed but could not be processed. For Payments
            this covers a missing payout address, a failed payment verification, a
            spend-policy decline, or an expired challenge; `error.code` distinguishes
            them.
        "429":
          $ref: "#/components/responses/RateLimited"
          description: Rate limit exceeded
      parameters:
        - name: Idempotency-Key
          in: header
          required: false
          description: Optional client-supplied idempotency key. Retrying a request with the same key returns the original result instead of performing the action a second time; if omitted the server derives one from the canonical payload hash. Safe to retry network failures without duplicating side effects.
          schema:
            type: string
            minLength: 1
            maxLength: 255
    get:
      operationId: listPayoutAddresses
      summary: List payout addresses
      description: List your org's registered payout addresses, newest first.
      tags:
        - Payments
      security:
        - BearerAuth: []
      responses:
        "200":
          description: Your registered payout addresses
          content:
            application/json:
              schema:
                allOf:
                  - type: object
                    properties:
                      success:
                        type: boolean
                        const: true
                    required:
                      - success
                      - data
                  - type: object
                    properties:
                      data:
                        type: array
                        items:
                          type: object
                          properties:
                            id:
                              type: string
                              format: uuid
                            address:
                              type: string
                              description: The checksummed payout address.
                            network:
                              type: string
                              enum:
                                - base
                                - base-sepolia
                            label:
                              type:
                                - string
                                - "null"
                            is_default:
                              type: boolean
                              description: Exactly one address per (org, network) is the default.
                            verified_at:
                              type: string
                              format: date-time
                              description: When ownership of the address was last proven.
                            created_at:
                              type: string
                              format: date-time
                          required:
                            - id
                            - address
                            - network
                            - label
                            - is_default
                            - verified_at
          headers:
            ratelimit-limit:
              description: Maximum number of requests allowed in the current window.
              schema:
                type: integer
                minimum: 1
                example: 120
            ratelimit-remaining:
              description: Remaining requests in the current window.
              schema:
                type: integer
                minimum: 0
                example: 118
            ratelimit-reset:
              description: Unix timestamp (seconds) when the current window resets.
              schema:
                type: integer
                example: 1700000060
            ratelimit-policy:
              description: Rate-limit policy in `limit;w=seconds` format, e.g. `120;w=60`.
              schema:
                type: string
                example: 120;w=60
        "401":
          $ref: "#/components/responses/Unauthorized"
          description: Invalid or missing API key
        "403":
          $ref: "#/components/responses/Forbidden"
          description: Authenticated caller lacks permission for the operation
        "429":
          $ref: "#/components/responses/RateLimited"
          description: Rate limit exceeded
  /x402/email-challenges:
    post:
      operationId: createEmailChallenge
      summary: Create an email-native payment challenge
      description: |
        Issue an x402 payment challenge over a real email thread (the payee
        side). Unlike `createChallenge` (which mints a synthetic challenge id),
        this sends the challenge as an email from `from` to `to` and binds the
        payment to that DKIM-authenticated thread. The `pay_to` address and the
        token asset are resolved server-side from your registered default payout
        address for the network, never from the request. The response carries
        the thread's `interaction_id` plus the `challenge` (the
        `payment_requirements`, the `nonce_binding`, and `expires_at`) the payer
        needs to sign; the payer replies with a signed `payment` interaction
        step. Amounts are in token base units (USDC has 6 decimals, so `"10000"`
        is 0.01 USDC).
      tags:
        - Payments
      security:
        - BearerAuth: []
      parameters:
        - name: Idempotency-Key
          in: header
          required: false
          description: Optional client-supplied idempotency key. Retrying a request with the same key returns the original result instead of performing the action a second time; if omitted the server derives one from the canonical payload hash. Safe to retry network failures without duplicating side effects.
          schema:
            type: string
            minLength: 1
            maxLength: 255
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              additionalProperties: false
              description: |
                Issue a payment challenge over an email thread. `from` is your sending
                address (the funds receiver; ownership is enforced at send, exactly as
                for outbound mail) and `to` is the payer's address. The `pay_to` payout
                wallet and the token asset are resolved server-side, never taken from
                the request.
              properties:
                from:
                  type: string
                  format: email
                  description: |
                    Your sending address (the payee / funds receiver). Must be an
                    address your org is allowed to send from.
                to:
                  type: string
                  format: email
                  description: The payer's email address the challenge is sent to.
                amount:
                  type: string
                  pattern: ^[1-9][0-9]{0,38}$
                  description: |
                    Amount to collect, in token base units (unlike the `charge` CLI
                    command, which also accepts `--amount-usdc`, this field takes base
                    units only). USDC has 6 decimals, so `"10000"` is 0.01 USDC:
                    multiply a human USDC amount by 1,000,000 (0.01 USDC -> `"10000"`).
                network:
                  type: string
                  enum:
                    - base
                    - base-sepolia
                expires_in:
                  type: integer
                  minimum: 60
                  maximum: 86400
                  description: Seconds until the challenge expires. Defaults to 300.
                resource:
                  type: string
                  format: uri
                  maxLength: 2048
                  description: Optional URL identifying what is being paid for.
                description:
                  type: string
                  maxLength: 512
                  description: Optional human-readable description of the payment.
              required:
                - from
                - to
                - amount
                - network
      responses:
        "200":
          description: |
            Idempotent replay: a request with a previously-used idempotency key
            returns the original issued challenge without sending a second email.
          content:
            application/json:
              schema:
                allOf:
                  - type: object
                    properties:
                      success:
                        type: boolean
                        const: true
                    required:
                      - success
                      - data
                  - type: object
                    properties:
                      data:
                        type: object
                        description: |
                          The result of issuing an email-native payment challenge. `interaction_id`
                          is the real email thread id (`uuid@domain`) the payment is bound to;
                          `challenge_id` is the underlying challenge record. Hand the `challenge`
                          to the payer, who replies with a signed `payment` interaction step (the
                          SDK `payEmailChallenge` helper builds it).
                        properties:
                          interaction_id:
                            type: string
                            description: The email thread id (`uuid@domain`) the payment is bound to.
                          challenge_id:
                            type: string
                            format: uuid
                            description: The underlying challenge record id.
                          challenge:
                            type: object
                            description: |
                              The challenge the payer needs to sign and pay, carried inside an
                              email-native challenge response.
                            properties:
                              payment_requirements:
                                type: object
                                description: |
                                  The x402 `PaymentRequirements` the payer signs over. Field names are
                                  x402's native camelCase, preserved byte-for-byte.
                                properties:
                                  scheme:
                                    type: string
                                    description: The x402 settlement scheme. Always `exact` for v1.
                                    example: exact
                                  network:
                                    type: string
                                    enum:
                                      - base
                                      - base-sepolia
                                  maxAmountRequired:
                                    type: string
                                    description: Amount in token base units.
                                  payTo:
                                    type: string
                                    description: The payee's resolved payout address (checksummed).
                                  asset:
                                    type: string
                                    description: The token contract address (checksummed). USDC.
                                  resource:
                                    type: string
                                  description:
                                    type: string
                                  maxTimeoutSeconds:
                                    type: integer
                                  extra:
                                    type: object
                                    description: |
                                      The token's load-bearing EIP-712 domain params. `name` differs by
                                      chain (Base mainnet USDC is `USD Coin`, Base Sepolia is `USDC`); a
                                      wrong value produces a signature the verifier rejects.
                                    properties:
                                      name:
                                        type: string
                                      version:
                                        type: string
                                    required:
                                      - name
                                      - version
                                required:
                                  - scheme
                                  - network
                                  - maxAmountRequired
                                  - payTo
                                  - asset
                                  - extra
                              nonce_binding:
                                type: object
                                description: |
                                  The interaction binding the payer hashes into the EIP-3009 nonce
                                  (`deriveEip3009Nonce`). Pinning the nonce to this binding is what lets an
                                  x402 payment ride asynchronous transports safely: a replayed challenge
                                  can't redirect funds and a signed payment can't settle twice.
                                properties:
                                  interaction_id:
                                    type: string
                                    description: Interaction id, including its `@domain` part.
                                  challenge_step_id:
                                    type: string
                                    format: uuid
                                  challenge_nonce:
                                    type: string
                                    pattern: ^[0-9a-f]{64}$
                                    description: 32 random bytes as 64 lowercase hex chars.
                                required:
                                  - interaction_id
                                  - challenge_step_id
                                  - challenge_nonce
                              expires_at:
                                type: string
                                format: date-time
                                description: ISO-8601 expiry of the challenge.
                            required:
                              - payment_requirements
                              - nonce_binding
                              - expires_at
                        required:
                          - interaction_id
                          - challenge_id
                          - challenge
          headers:
            ratelimit-limit:
              description: Maximum number of requests allowed in the current window.
              schema:
                type: integer
                minimum: 1
                example: 120
            ratelimit-remaining:
              description: Remaining requests in the current window.
              schema:
                type: integer
                minimum: 0
                example: 118
            ratelimit-reset:
              description: Unix timestamp (seconds) when the current window resets.
              schema:
                type: integer
                example: 1700000060
            ratelimit-policy:
              description: Rate-limit policy in `limit;w=seconds` format, e.g. `120;w=60`.
              schema:
                type: string
                example: 120;w=60
        "201":
          description: Email challenge issued
          content:
            application/json:
              schema:
                allOf:
                  - type: object
                    properties:
                      success:
                        type: boolean
                        const: true
                    required:
                      - success
                      - data
                  - type: object
                    properties:
                      data:
                        type: object
                        description: |
                          The result of issuing an email-native payment challenge. `interaction_id`
                          is the real email thread id (`uuid@domain`) the payment is bound to;
                          `challenge_id` is the underlying challenge record. Hand the `challenge`
                          to the payer, who replies with a signed `payment` interaction step (the
                          SDK `payEmailChallenge` helper builds it).
                        properties:
                          interaction_id:
                            type: string
                            description: The email thread id (`uuid@domain`) the payment is bound to.
                          challenge_id:
                            type: string
                            format: uuid
                            description: The underlying challenge record id.
                          challenge:
                            type: object
                            description: |
                              The challenge the payer needs to sign and pay, carried inside an
                              email-native challenge response.
                            properties:
                              payment_requirements:
                                type: object
                                description: |
                                  The x402 `PaymentRequirements` the payer signs over. Field names are
                                  x402's native camelCase, preserved byte-for-byte.
                                properties:
                                  scheme:
                                    type: string
                                    description: The x402 settlement scheme. Always `exact` for v1.
                                    example: exact
                                  network:
                                    type: string
                                    enum:
                                      - base
                                      - base-sepolia
                                  maxAmountRequired:
                                    type: string
                                    description: Amount in token base units.
                                  payTo:
                                    type: string
                                    description: The payee's resolved payout address (checksummed).
                                  asset:
                                    type: string
                                    description: The token contract address (checksummed). USDC.
                                  resource:
                                    type: string
                                  description:
                                    type: string
                                  maxTimeoutSeconds:
                                    type: integer
                                  extra:
                                    type: object
                                    description: |
                                      The token's load-bearing EIP-712 domain params. `name` differs by
                                      chain (Base mainnet USDC is `USD Coin`, Base Sepolia is `USDC`); a
                                      wrong value produces a signature the verifier rejects.
                                    properties:
                                      name:
                                        type: string
                                      version:
                                        type: string
                                    required:
                                      - name
                                      - version
                                required:
                                  - scheme
                                  - network
                                  - maxAmountRequired
                                  - payTo
                                  - asset
                                  - extra
                              nonce_binding:
                                type: object
                                description: |
                                  The interaction binding the payer hashes into the EIP-3009 nonce
                                  (`deriveEip3009Nonce`). Pinning the nonce to this binding is what lets an
                                  x402 payment ride asynchronous transports safely: a replayed challenge
                                  can't redirect funds and a signed payment can't settle twice.
                                properties:
                                  interaction_id:
                                    type: string
                                    description: Interaction id, including its `@domain` part.
                                  challenge_step_id:
                                    type: string
                                    format: uuid
                                  challenge_nonce:
                                    type: string
                                    pattern: ^[0-9a-f]{64}$
                                    description: 32 random bytes as 64 lowercase hex chars.
                                required:
                                  - interaction_id
                                  - challenge_step_id
                                  - challenge_nonce
                              expires_at:
                                type: string
                                format: date-time
                                description: ISO-8601 expiry of the challenge.
                            required:
                              - payment_requirements
                              - nonce_binding
                              - expires_at
                        required:
                          - interaction_id
                          - challenge_id
                          - challenge
        "400":
          $ref: "#/components/responses/ValidationError"
          description: Invalid request parameters
        "401":
          $ref: "#/components/responses/Unauthorized"
          description: Invalid or missing API key
        "403":
          $ref: "#/components/responses/Forbidden"
          description: Authenticated caller lacks permission for the operation
        "409":
          $ref: "#/components/responses/Conflict"
          description: The request conflicts with the current state of the resource
        "422":
          $ref: "#/components/responses/UnprocessableEntity"
          description: |
            The request was well-formed but could not be processed. For Payments
            this covers a missing payout address, a failed payment verification, a
            spend-policy decline, or an expired challenge; `error.code` distinguishes
            them.
        "429":
          $ref: "#/components/responses/RateLimited"
          description: Rate limit exceeded
  /x402/challenges:
    post:
      operationId: createChallenge
      summary: Create a payment challenge
      description: |
        Create an x402 payment challenge (the payee side of a payment). The
        `pay_to` address is resolved server-side from your registered default
        payout address for the network, never from the request. The response
        carries the `nonce_binding` and `payment_requirements` the payer needs to
        sign; hand the whole challenge object to the payer (for example in an
        email reply). Amounts are in token base units (USDC has 6 decimals, so
        `"10000"` is 0.01 USDC).
      tags:
        - Payments
      security:
        - BearerAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              additionalProperties: false
              properties:
                amount:
                  type: string
                  pattern: ^[1-9][0-9]{0,38}$
                  description: |
                    Amount to collect, in token base units. USDC has 6 decimals, so
                    `"10000"` is 0.01 USDC.
                network:
                  type: string
                  enum:
                    - base
                    - base-sepolia
                payer_org:
                  type: string
                  format: uuid
                  description: |
                    The org id allowed to pay this challenge (on-net binding). Optional.
                expires_in:
                  type: integer
                  minimum: 60
                  maximum: 86400
                  description: Seconds until the challenge expires. Defaults to 3600.
                resource:
                  type: string
                  format: uri
                  maxLength: 2048
                  description: |
                    Optional URL identifying what is being paid for. Defaults to a
                    synthetic `x402:challenge:<id>` identifier.
                description:
                  type: string
                  maxLength: 512
                  description: Optional human-readable description of the payment.
              required:
                - amount
                - network
      responses:
        "201":
          description: Challenge created
          content:
            application/json:
              schema:
                allOf:
                  - type: object
                    properties:
                      success:
                        type: boolean
                        const: true
                    required:
                      - success
                      - data
                  - type: object
                    properties:
                      data:
                        type: object
                        properties:
                          id:
                            type: string
                            format: uuid
                          status:
                            type: string
                            enum:
                              - pending
                              - settling
                              - settled
                              - failed
                              - expired
                          network:
                            type: string
                            enum:
                              - base
                              - base-sepolia
                          asset:
                            type: string
                            description: Token contract address (checksummed).
                          amount:
                            type: string
                            description: Amount in token base units.
                          pay_to:
                            type: string
                            description: The payee's resolved payout address (checksummed).
                          payer_org:
                            type:
                              - string
                              - "null"
                            description: The org id bound as payer, if one was set at creation.
                          resource:
                            type:
                              - string
                              - "null"
                          description:
                            type:
                              - string
                              - "null"
                          nonce_binding:
                            type: object
                            description: |
                              The interaction binding the payer hashes into the EIP-3009 nonce
                              (`deriveEip3009Nonce`). Pinning the nonce to this binding is what lets an
                              x402 payment ride asynchronous transports safely: a replayed challenge
                              can't redirect funds and a signed payment can't settle twice.
                            properties:
                              interaction_id:
                                type: string
                                description: Interaction id, including its `@domain` part.
                              challenge_step_id:
                                type: string
                                format: uuid
                              challenge_nonce:
                                type: string
                                pattern: ^[0-9a-f]{64}$
                                description: 32 random bytes as 64 lowercase hex chars.
                            required:
                              - interaction_id
                              - challenge_step_id
                              - challenge_nonce
                          settle_tx:
                            type:
                              - string
                              - "null"
                            description: On-chain settlement transaction hash once settled.
                          settled_at:
                            type:
                              - string
                              - "null"
                            format: date-time
                          failure_reason:
                            type:
                              - string
                              - "null"
                          expires_at:
                            type: string
                            format: date-time
                          created_at:
                            type: string
                            format: date-time
                          payment_requirements:
                            description: |
                              Present on the create response. Hand the whole challenge (including
                              this) to the payer; `getChallenge` omits it (it is for status polling
                              by the challenger).
                            allOf:
                              - type: object
                                description: |
                                  The x402 `PaymentRequirements` the payer signs over. Field names are
                                  x402's native camelCase, preserved byte-for-byte.
                                properties:
                                  scheme:
                                    type: string
                                    description: The x402 settlement scheme. Always `exact` for v1.
                                    example: exact
                                  network:
                                    type: string
                                    enum:
                                      - base
                                      - base-sepolia
                                  maxAmountRequired:
                                    type: string
                                    description: Amount in token base units.
                                  payTo:
                                    type: string
                                    description: The payee's resolved payout address (checksummed).
                                  asset:
                                    type: string
                                    description: The token contract address (checksummed). USDC.
                                  resource:
                                    type: string
                                  description:
                                    type: string
                                  maxTimeoutSeconds:
                                    type: integer
                                  extra:
                                    type: object
                                    description: |
                                      The token's load-bearing EIP-712 domain params. `name` differs by
                                      chain (Base mainnet USDC is `USD Coin`, Base Sepolia is `USDC`); a
                                      wrong value produces a signature the verifier rejects.
                                    properties:
                                      name:
                                        type: string
                                      version:
                                        type: string
                                    required:
                                      - name
                                      - version
                                required:
                                  - scheme
                                  - network
                                  - maxAmountRequired
                                  - payTo
                                  - asset
                                  - extra
                        required:
                          - id
                          - status
                          - network
                          - asset
                          - amount
                          - pay_to
                          - nonce_binding
                          - expires_at
        "400":
          $ref: "#/components/responses/ValidationError"
          description: Invalid request parameters
        "401":
          $ref: "#/components/responses/Unauthorized"
          description: Invalid or missing API key
        "403":
          $ref: "#/components/responses/Forbidden"
          description: Authenticated caller lacks permission for the operation
        "422":
          $ref: "#/components/responses/UnprocessableEntity"
          description: |
            The request was well-formed but could not be processed. For Payments
            this covers a missing payout address, a failed payment verification, a
            spend-policy decline, or an expired challenge; `error.code` distinguishes
            them.
        "429":
          $ref: "#/components/responses/RateLimited"
          description: Rate limit exceeded
      parameters:
        - name: Idempotency-Key
          in: header
          required: false
          description: Optional client-supplied idempotency key. Retrying a request with the same key returns the original result instead of performing the action a second time; if omitted the server derives one from the canonical payload hash. Safe to retry network failures without duplicating side effects.
          schema:
            type: string
            minLength: 1
            maxLength: 255
  /x402/challenges/{id}:
    get:
      operationId: getChallenge
      summary: Get a payment challenge
      description: |
        Fetch a challenge you created, to poll its `status` and settlement
        receipt (`settle_tx`). Scoped to the challenger org that created it.
      tags:
        - Payments
      security:
        - BearerAuth: []
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
            format: uuid
          description: Resource UUID
      responses:
        "200":
          description: The challenge
          content:
            application/json:
              schema:
                allOf:
                  - type: object
                    properties:
                      success:
                        type: boolean
                        const: true
                    required:
                      - success
                      - data
                  - type: object
                    properties:
                      data:
                        type: object
                        properties:
                          id:
                            type: string
                            format: uuid
                          status:
                            type: string
                            enum:
                              - pending
                              - settling
                              - settled
                              - failed
                              - expired
                          network:
                            type: string
                            enum:
                              - base
                              - base-sepolia
                          asset:
                            type: string
                            description: Token contract address (checksummed).
                          amount:
                            type: string
                            description: Amount in token base units.
                          pay_to:
                            type: string
                            description: The payee's resolved payout address (checksummed).
                          payer_org:
                            type:
                              - string
                              - "null"
                            description: The org id bound as payer, if one was set at creation.
                          resource:
                            type:
                              - string
                              - "null"
                          description:
                            type:
                              - string
                              - "null"
                          nonce_binding:
                            type: object
                            description: |
                              The interaction binding the payer hashes into the EIP-3009 nonce
                              (`deriveEip3009Nonce`). Pinning the nonce to this binding is what lets an
                              x402 payment ride asynchronous transports safely: a replayed challenge
                              can't redirect funds and a signed payment can't settle twice.
                            properties:
                              interaction_id:
                                type: string
                                description: Interaction id, including its `@domain` part.
                              challenge_step_id:
                                type: string
                                format: uuid
                              challenge_nonce:
                                type: string
                                pattern: ^[0-9a-f]{64}$
                                description: 32 random bytes as 64 lowercase hex chars.
                            required:
                              - interaction_id
                              - challenge_step_id
                              - challenge_nonce
                          settle_tx:
                            type:
                              - string
                              - "null"
                            description: On-chain settlement transaction hash once settled.
                          settled_at:
                            type:
                              - string
                              - "null"
                            format: date-time
                          failure_reason:
                            type:
                              - string
                              - "null"
                          expires_at:
                            type: string
                            format: date-time
                          created_at:
                            type: string
                            format: date-time
                          payment_requirements:
                            description: |
                              Present on the create response. Hand the whole challenge (including
                              this) to the payer; `getChallenge` omits it (it is for status polling
                              by the challenger).
                            allOf:
                              - type: object
                                description: |
                                  The x402 `PaymentRequirements` the payer signs over. Field names are
                                  x402's native camelCase, preserved byte-for-byte.
                                properties:
                                  scheme:
                                    type: string
                                    description: The x402 settlement scheme. Always `exact` for v1.
                                    example: exact
                                  network:
                                    type: string
                                    enum:
                                      - base
                                      - base-sepolia
                                  maxAmountRequired:
                                    type: string
                                    description: Amount in token base units.
                                  payTo:
                                    type: string
                                    description: The payee's resolved payout address (checksummed).
                                  asset:
                                    type: string
                                    description: The token contract address (checksummed). USDC.
                                  resource:
                                    type: string
                                  description:
                                    type: string
                                  maxTimeoutSeconds:
                                    type: integer
                                  extra:
                                    type: object
                                    description: |
                                      The token's load-bearing EIP-712 domain params. `name` differs by
                                      chain (Base mainnet USDC is `USD Coin`, Base Sepolia is `USDC`); a
                                      wrong value produces a signature the verifier rejects.
                                    properties:
                                      name:
                                        type: string
                                      version:
                                        type: string
                                    required:
                                      - name
                                      - version
                                required:
                                  - scheme
                                  - network
                                  - maxAmountRequired
                                  - payTo
                                  - asset
                                  - extra
                        required:
                          - id
                          - status
                          - network
                          - asset
                          - amount
                          - pay_to
                          - nonce_binding
                          - expires_at
          headers:
            ratelimit-limit:
              description: Maximum number of requests allowed in the current window.
              schema:
                type: integer
                minimum: 1
                example: 120
            ratelimit-remaining:
              description: Remaining requests in the current window.
              schema:
                type: integer
                minimum: 0
                example: 118
            ratelimit-reset:
              description: Unix timestamp (seconds) when the current window resets.
              schema:
                type: integer
                example: 1700000060
            ratelimit-policy:
              description: Rate-limit policy in `limit;w=seconds` format, e.g. `120;w=60`.
              schema:
                type: string
                example: 120;w=60
        "400":
          $ref: "#/components/responses/ValidationError"
          description: Invalid request parameters
        "401":
          $ref: "#/components/responses/Unauthorized"
          description: Invalid or missing API key
        "403":
          $ref: "#/components/responses/Forbidden"
          description: Authenticated caller lacks permission for the operation
        "404":
          $ref: "#/components/responses/NotFound"
          description: Resource not found
        "429":
          $ref: "#/components/responses/RateLimited"
          description: Rate limit exceeded
  /x402/challenges/{id}/pay:
    post:
      operationId: payChallenge
      summary: Pay a payment challenge
      description: |
        Settle a challenge addressed to your org as payer. The request body
        carries a signed x402 `PaymentPayload`: an EIP-3009
        `transferWithAuthorization` signed locally with your own key, whose nonce
        is bound to the challenge via the SDK's `deriveEip3009Nonce`. The platform
        verifies every signed field against its own record of the challenge,
        applies your spend policy, and settles on-chain through a facilitator.
        Settlement is non-custodial; Primitive never holds funds. Idempotent:
        paying an already-settled challenge returns the original receipt. Most
        callers use the SDK `pay()` helper rather than building the payload by
        hand.
      tags:
        - Payments
      security:
        - BearerAuth: []
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
            format: uuid
          description: Resource UUID
        - name: Idempotency-Key
          in: header
          required: false
          description: Optional client-supplied idempotency key. Retrying a request with the same key returns the original result instead of performing the action a second time; if omitted the server derives one from the canonical payload hash. Safe to retry network failures without duplicating side effects.
          schema:
            type: string
            minLength: 1
            maxLength: 255
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              additionalProperties: false
              properties:
                payment:
                  type: object
                  description: |
                    A signed x402 v1 `PaymentPayload`. The SDK `pay()` helper builds this;
                    callers rarely construct it by hand. Field names are x402-native.
                  properties:
                    x402Version:
                      type: integer
                      const: 1
                    scheme:
                      type: string
                      const: exact
                    network:
                      type: string
                      enum:
                        - base
                        - base-sepolia
                    payload:
                      type: object
                      properties:
                        signature:
                          type: string
                          pattern: ^0x[0-9a-fA-F]+$
                          description: The EIP-712 signature over the authorization.
                        authorization:
                          type: object
                          description: The EIP-3009 `transferWithAuthorization` fields, as strings.
                          properties:
                            from:
                              type: string
                            to:
                              type: string
                            value:
                              type: string
                            validAfter:
                              type: string
                            validBefore:
                              type: string
                            nonce:
                              type: string
                          required:
                            - from
                            - to
                            - value
                            - validAfter
                            - validBefore
                            - nonce
                      required:
                        - signature
                        - authorization
                  required:
                    - x402Version
                    - scheme
                    - network
                    - payload
              required:
                - payment
      responses:
        "200":
          description: Challenge settled (or already settled)
          content:
            application/json:
              schema:
                allOf:
                  - type: object
                    properties:
                      success:
                        type: boolean
                        const: true
                    required:
                      - success
                      - data
                  - type: object
                    properties:
                      data:
                        type: object
                        properties:
                          id:
                            type: string
                            format: uuid
                          status:
                            type: string
                            enum:
                              - settled
                          settle_tx:
                            type:
                              - string
                              - "null"
                            description: On-chain settlement transaction hash.
                        required:
                          - id
                          - status
                          - settle_tx
          headers:
            ratelimit-limit:
              description: Maximum number of requests allowed in the current window.
              schema:
                type: integer
                minimum: 1
                example: 120
            ratelimit-remaining:
              description: Remaining requests in the current window.
              schema:
                type: integer
                minimum: 0
                example: 118
            ratelimit-reset:
              description: Unix timestamp (seconds) when the current window resets.
              schema:
                type: integer
                example: 1700000060
            ratelimit-policy:
              description: Rate-limit policy in `limit;w=seconds` format, e.g. `120;w=60`.
              schema:
                type: string
                example: 120;w=60
        "400":
          $ref: "#/components/responses/ValidationError"
          description: Invalid request parameters
        "401":
          $ref: "#/components/responses/Unauthorized"
          description: Invalid or missing API key
        "403":
          $ref: "#/components/responses/Forbidden"
          description: Authenticated caller lacks permission for the operation
        "404":
          $ref: "#/components/responses/NotFound"
          description: Resource not found
        "409":
          $ref: "#/components/responses/Conflict"
          description: The request conflicts with the current state of the resource
        "422":
          $ref: "#/components/responses/UnprocessableEntity"
          description: |
            The request was well-formed but could not be processed. For Payments
            this covers a missing payout address, a failed payment verification, a
            spend-policy decline, or an expired challenge; `error.code` distinguishes
            them.
        "429":
          $ref: "#/components/responses/RateLimited"
          description: Rate limit exceeded
        "502":
          $ref: "#/components/responses/BadGateway"
          description: Primitive could not complete the downstream SMTP request
  /x402/spend-policy:
    get:
      operationId: getSpendPolicy
      summary: Get your spend policy
      description: |
        Read your org's outbound spend policy: the kill-switch, per-payment and
        per-day caps, and the payee allowlist. Returns the defaults (no limits,
        not paused) when no policy has been set.
      tags:
        - Payments
      security:
        - BearerAuth: []
      responses:
        "200":
          description: The spend policy
          content:
            application/json:
              schema:
                allOf:
                  - type: object
                    properties:
                      success:
                        type: boolean
                        const: true
                    required:
                      - success
                      - data
                  - type: object
                    properties:
                      data:
                        type: object
                        description: |
                          The payer's outbound spend policy. Returned with defaults (not paused,
                          no caps, any on-net payee) when none is set.
                        properties:
                          paused:
                            type: boolean
                            description: Kill-switch. When true, all outbound payments are refused.
                          max_per_payment:
                            type:
                              - string
                              - "null"
                            description: Per-payment cap in token base units, or null for no cap.
                          max_per_day:
                            type:
                              - string
                              - "null"
                            description: Rolling-day cap in token base units, or null for no cap.
                          allowlist:
                            type:
                              - array
                              - "null"
                            items:
                              type: string
                              format: uuid
                            description: |
                              Allowed payee org ids. `null` allows any on-net payee; `[]` denies
                              all.
                        required:
                          - paused
                          - max_per_payment
                          - max_per_day
                          - allowlist
          headers:
            ratelimit-limit:
              description: Maximum number of requests allowed in the current window.
              schema:
                type: integer
                minimum: 1
                example: 120
            ratelimit-remaining:
              description: Remaining requests in the current window.
              schema:
                type: integer
                minimum: 0
                example: 118
            ratelimit-reset:
              description: Unix timestamp (seconds) when the current window resets.
              schema:
                type: integer
                example: 1700000060
            ratelimit-policy:
              description: Rate-limit policy in `limit;w=seconds` format, e.g. `120;w=60`.
              schema:
                type: string
                example: 120;w=60
        "401":
          $ref: "#/components/responses/Unauthorized"
          description: Invalid or missing API key
        "403":
          $ref: "#/components/responses/Forbidden"
          description: Authenticated caller lacks permission for the operation
        "429":
          $ref: "#/components/responses/RateLimited"
          description: Rate limit exceeded
    put:
      operationId: updateSpendPolicy
      summary: Update your spend policy
      description: |
        Update your org's spend policy. Applied as a merge: only the fields you
        include change, and omitted fields keep their current value, so a partial
        update can't silently reset the kill-switch. Send an explicit `null` to
        clear a cap. Caps are in token base units.
      tags:
        - Payments
      security:
        - BearerAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              additionalProperties: false
              description: |
                Merge update: only the fields you include change; omit a field to keep
                its current value; send `null` to clear a cap.
              properties:
                paused:
                  type: boolean
                max_per_payment:
                  type:
                    - string
                    - "null"
                  pattern: ^[1-9][0-9]{0,38}$
                max_per_day:
                  type:
                    - string
                    - "null"
                  pattern: ^[1-9][0-9]{0,38}$
                allowlist:
                  type:
                    - array
                    - "null"
                  maxItems: 1000
                  items:
                    type: string
                    format: uuid
      responses:
        "200":
          description: The updated spend policy
          content:
            application/json:
              schema:
                allOf:
                  - type: object
                    properties:
                      success:
                        type: boolean
                        const: true
                    required:
                      - success
                      - data
                  - type: object
                    properties:
                      data:
                        type: object
                        description: |
                          The payer's outbound spend policy. Returned with defaults (not paused,
                          no caps, any on-net payee) when none is set.
                        properties:
                          paused:
                            type: boolean
                            description: Kill-switch. When true, all outbound payments are refused.
                          max_per_payment:
                            type:
                              - string
                              - "null"
                            description: Per-payment cap in token base units, or null for no cap.
                          max_per_day:
                            type:
                              - string
                              - "null"
                            description: Rolling-day cap in token base units, or null for no cap.
                          allowlist:
                            type:
                              - array
                              - "null"
                            items:
                              type: string
                              format: uuid
                            description: |
                              Allowed payee org ids. `null` allows any on-net payee; `[]` denies
                              all.
                        required:
                          - paused
                          - max_per_payment
                          - max_per_day
                          - allowlist
          headers:
            ratelimit-limit:
              description: Maximum number of requests allowed in the current window.
              schema:
                type: integer
                minimum: 1
                example: 120
            ratelimit-remaining:
              description: Remaining requests in the current window.
              schema:
                type: integer
                minimum: 0
                example: 118
            ratelimit-reset:
              description: Unix timestamp (seconds) when the current window resets.
              schema:
                type: integer
                example: 1700000060
            ratelimit-policy:
              description: Rate-limit policy in `limit;w=seconds` format, e.g. `120;w=60`.
              schema:
                type: string
                example: 120;w=60
        "400":
          $ref: "#/components/responses/ValidationError"
          description: Invalid request parameters
        "401":
          $ref: "#/components/responses/Unauthorized"
          description: Invalid or missing API key
        "403":
          $ref: "#/components/responses/Forbidden"
          description: Authenticated caller lacks permission for the operation
        "429":
          $ref: "#/components/responses/RateLimited"
          description: Rate limit exceeded
  /x402/declined-payments:
    get:
      operationId: listDeclinedPayments
      summary: List declined payments
      description: |
        The 50 most recent payments your org's spend policy declined, newest
        first. Use this to see why an outbound payment was refused (a cap, the
        payee allowlist, or the kill-switch) instead of only reading the
        dashboard.
      tags:
        - Payments
      security:
        - BearerAuth: []
      responses:
        "200":
          description: Recently declined payments
          content:
            application/json:
              schema:
                allOf:
                  - type: object
                    properties:
                      success:
                        type: boolean
                        const: true
                    required:
                      - success
                      - data
                  - type: object
                    properties:
                      data:
                        type: array
                        items:
                          type: object
                          description: A payment the org's spend policy refused.
                          properties:
                            id:
                              type: string
                              format: uuid
                            challenge_id:
                              type:
                                - string
                                - "null"
                              format: uuid
                              description: The challenge that was declined, if still present.
                            counterparty_org:
                              type:
                                - string
                                - "null"
                              format: uuid
                              description: The payee (challenger) org, when known.
                            network:
                              type: string
                              enum:
                                - base
                                - base-sepolia
                            amount:
                              type: string
                              description: Amount in token base units.
                            reason:
                              type: string
                              description: Why the payment was declined (cap, allowlist, paused).
                            declined_at:
                              type: string
                              format: date-time
                          required:
                            - id
                            - network
                            - amount
                            - reason
                            - declined_at
          headers:
            ratelimit-limit:
              description: Maximum number of requests allowed in the current window.
              schema:
                type: integer
                minimum: 1
                example: 120
            ratelimit-remaining:
              description: Remaining requests in the current window.
              schema:
                type: integer
                minimum: 0
                example: 118
            ratelimit-reset:
              description: Unix timestamp (seconds) when the current window resets.
              schema:
                type: integer
                example: 1700000060
            ratelimit-policy:
              description: Rate-limit policy in `limit;w=seconds` format, e.g. `120;w=60`.
              schema:
                type: string
                example: 120;w=60
        "401":
          $ref: "#/components/responses/Unauthorized"
          description: Invalid or missing API key
        "403":
          $ref: "#/components/responses/Forbidden"
          description: Authenticated caller lacks permission for the operation
        "429":
          $ref: "#/components/responses/RateLimited"
          description: Rate limit exceeded
  /registries:
    get:
      operationId: listRegistries
      summary: List the registries you own
      tags:
        - Registries
      responses:
        "200":
          description: Registries owned by the caller
          content:
            application/json:
              schema:
                allOf:
                  - type: object
                    properties:
                      success:
                        type: boolean
                        const: true
                    required:
                      - success
                      - data
                  - properties:
                      data:
                        type: array
                        items:
                          type: object
                          properties:
                            id:
                              type: string
                              format: uuid
                            slug:
                              type: string
                            name:
                              type: string
                            description:
                              type:
                                - string
                                - "null"
                            is_public:
                              type: boolean
                            publish_policy:
                              type: string
                              enum:
                                - owner_only
                                - request
                                - open
                              description: |
                                Who may publish into a registry. owner_only: only the registry owner.
                                request: anyone may request and the owner approves. open: anyone may
                                publish and it lists immediately (no approval step).
                          required:
                            - id
                            - slug
                            - name
                            - description
                            - is_public
                            - publish_policy
          headers:
            ratelimit-limit:
              description: Maximum number of requests allowed in the current window.
              schema:
                type: integer
                minimum: 1
                example: 120
            ratelimit-remaining:
              description: Remaining requests in the current window.
              schema:
                type: integer
                minimum: 0
                example: 118
            ratelimit-reset:
              description: Unix timestamp (seconds) when the current window resets.
              schema:
                type: integer
                example: 1700000060
            ratelimit-policy:
              description: Rate-limit policy in `limit;w=seconds` format, e.g. `120;w=60`.
              schema:
                type: string
                example: 120;w=60
        "401":
          $ref: "#/components/responses/Unauthorized"
          description: Invalid or missing API key
      security:
        - BearerAuth: []
      description: List the registries you own
    post:
      operationId: createRegistry
      summary: Create a registry
      tags:
        - Registries
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                slug:
                  type: string
                  pattern: ^[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?$
                  description: Lowercase slug, unique across registries.
                name:
                  type: string
                  minLength: 1
                  maxLength: 120
                description:
                  type:
                    - string
                    - "null"
                  maxLength: 2000
                is_public:
                  type: boolean
                publish_policy:
                  type: string
                  enum:
                    - owner_only
                    - request
                    - open
                  description: |
                    Who may publish into a registry. owner_only: only the registry owner.
                    request: anyone may request and the owner approves. open: anyone may
                    publish and it lists immediately (no approval step).
              required:
                - slug
                - name
      responses:
        "201":
          description: Registry created
          content:
            application/json:
              schema:
                allOf:
                  - type: object
                    properties:
                      success:
                        type: boolean
                        const: true
                    required:
                      - success
                      - data
                  - properties:
                      data:
                        type: object
                        properties:
                          id:
                            type: string
                            format: uuid
                          slug:
                            type: string
                        required:
                          - id
                          - slug
        "401":
          $ref: "#/components/responses/Unauthorized"
          description: Invalid or missing API key
        "403":
          $ref: "#/components/responses/Forbidden"
          description: Authenticated caller lacks permission for the operation
        "409":
          $ref: "#/components/responses/Conflict"
          description: The request conflicts with the current state of the resource
        "422":
          $ref: "#/components/responses/UnprocessableEntity"
          description: Invalid request parameters
      security:
        - BearerAuth: []
      parameters:
        - name: Idempotency-Key
          in: header
          required: false
          description: Optional client-supplied idempotency key. Retrying a request with the same key returns the original result instead of performing the action a second time; if omitted the server derives one from the canonical payload hash. Safe to retry network failures without duplicating side effects.
          schema:
            type: string
            minLength: 1
            maxLength: 255
      description: Create a registry
  /registries/{slug}:
    parameters:
      - name: slug
        in: path
        required: true
        schema:
          type: string
        description: The registry slug
    get:
      operationId: getRegistry
      summary: Get a public registry's metadata
      tags:
        - Registries
      security: []
      responses:
        "200":
          description: Registry metadata
          content:
            application/json:
              schema:
                allOf:
                  - type: object
                    properties:
                      success:
                        type: boolean
                        const: true
                    required:
                      - success
                      - data
                  - properties:
                      data:
                        type: object
                        properties:
                          id:
                            type: string
                            format: uuid
                          slug:
                            type: string
                          name:
                            type: string
                          description:
                            type:
                              - string
                              - "null"
                          is_public:
                            type: boolean
                          publish_policy:
                            type: string
                            enum:
                              - owner_only
                              - request
                              - open
                            description: |
                              Who may publish into a registry. owner_only: only the registry owner.
                              request: anyone may request and the owner approves. open: anyone may
                              publish and it lists immediately (no approval step).
                        required:
                          - id
                          - slug
                          - name
                          - description
                          - is_public
                          - publish_policy
        "404":
          $ref: "#/components/responses/NotFound"
          description: Resource not found
      description: Get a public registry's metadata
    patch:
      operationId: updateRegistry
      summary: Update a registry you own
      tags:
        - Registries
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              minProperties: 1
              properties:
                name:
                  type: string
                  minLength: 1
                  maxLength: 120
                description:
                  type:
                    - string
                    - "null"
                  maxLength: 2000
                is_public:
                  type: boolean
                publish_policy:
                  type: string
                  enum:
                    - owner_only
                    - request
                    - open
                  description: |
                    Who may publish into a registry. owner_only: only the registry owner.
                    request: anyone may request and the owner approves. open: anyone may
                    publish and it lists immediately (no approval step).
      responses:
        "200":
          description: Registry updated
          content:
            application/json:
              schema:
                allOf:
                  - type: object
                    properties:
                      success:
                        type: boolean
                        const: true
                    required:
                      - success
                      - data
                  - properties:
                      data:
                        type: object
                        properties:
                          id:
                            type: string
                            format: uuid
                        required:
                          - id
          headers:
            ratelimit-limit:
              description: Maximum number of requests allowed in the current window.
              schema:
                type: integer
                minimum: 1
                example: 120
            ratelimit-remaining:
              description: Remaining requests in the current window.
              schema:
                type: integer
                minimum: 0
                example: 118
            ratelimit-reset:
              description: Unix timestamp (seconds) when the current window resets.
              schema:
                type: integer
                example: 1700000060
            ratelimit-policy:
              description: Rate-limit policy in `limit;w=seconds` format, e.g. `120;w=60`.
              schema:
                type: string
                example: 120;w=60
        "401":
          $ref: "#/components/responses/Unauthorized"
          description: Invalid or missing API key
        "403":
          $ref: "#/components/responses/Forbidden"
          description: Authenticated caller lacks permission for the operation
        "404":
          $ref: "#/components/responses/NotFound"
          description: Resource not found
        "422":
          $ref: "#/components/responses/UnprocessableEntity"
          description: Invalid request parameters
      security:
        - BearerAuth: []
      description: Update a registry you own
    delete:
      operationId: deleteRegistry
      summary: Delete a registry you own
      description: Removes the registry from discovery and frees its slug for re-creation.
      tags:
        - Registries
      responses:
        "200":
          description: Resource deleted
          content:
            application/json:
              schema:
                allOf:
                  - type: object
                    properties:
                      success:
                        type: boolean
                        const: true
                    required:
                      - success
                      - data
                  - type: object
                    properties:
                      data:
                        type: object
                        properties:
                          deleted:
                            type: boolean
                            const: true
                        required:
                          - deleted
          headers:
            ratelimit-limit:
              description: Maximum number of requests allowed in the current window.
              schema:
                type: integer
                minimum: 1
                example: 120
            ratelimit-remaining:
              description: Remaining requests in the current window.
              schema:
                type: integer
                minimum: 0
                example: 118
            ratelimit-reset:
              description: Unix timestamp (seconds) when the current window resets.
              schema:
                type: integer
                example: 1700000060
            ratelimit-policy:
              description: Rate-limit policy in `limit;w=seconds` format, e.g. `120;w=60`.
              schema:
                type: string
                example: 120;w=60
        "401":
          $ref: "#/components/responses/Unauthorized"
          description: Invalid or missing API key
        "404":
          $ref: "#/components/responses/NotFound"
          description: Resource not found
      security:
        - BearerAuth: []
  /registries/{slug}/agents:
    parameters:
      - name: slug
        in: path
        required: true
        schema:
          type: string
        description: The registry slug.
    get:
      operationId: listRegistryAgents
      summary: List agents in a registry
      tags:
        - Registries
      security: []
      parameters:
        - name: limit
          in: query
          required: false
          schema:
            type: integer
            minimum: 1
            maximum: 200
          description: Maximum number of items to return (1-200).
        - name: cursor
          in: query
          required: false
          schema:
            type: string
          description: The address of the last agent from the previous page.
      responses:
        "200":
          description: Approved, reachable agents. Empty for an unknown or private registry.
          content:
            application/json:
              schema:
                allOf:
                  - type: object
                    properties:
                      success:
                        type: boolean
                        const: true
                      meta:
                        type: object
                        properties:
                          total:
                            type: integer
                            description: Total number of matching records
                          limit:
                            type: integer
                            description: Page size used for this request
                          cursor:
                            type:
                              - string
                              - "null"
                            description: Cursor for the next page, or null if no more results
                        required:
                          - total
                          - limit
                          - cursor
                    required:
                      - success
                      - data
                      - meta
                  - properties:
                      data:
                        type: array
                        items:
                          type: object
                          description: An agent's public directory profile.
                          properties:
                            address:
                              type: string
                            display_name:
                              type: string
                            title:
                              type:
                                - string
                                - "null"
                            description:
                              type:
                                - string
                                - "null"
                            tags:
                              type: array
                              items:
                                type: string
                            handle:
                              type:
                                - string
                                - "null"
                              description: The registry-scoped name. Null on the global by-address read.
                            last_reachable_at:
                              type:
                                - string
                                - "null"
                              format: date-time
                              description: When the agent's address was last confirmed to still route to its endpoint. A freshness signal for ranking listings; null until the first check.
                          required:
                            - address
                            - display_name
                            - title
                            - description
                            - tags
                            - handle
                            - last_reachable_at
      description: List agents in a registry
    post:
      operationId: publishAgent
      summary: Publish an agent into a registry
      tags:
        - Registries
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              description: Publish an agent into a registry. When display_name is present the agent identity is defined (create-or-get by address) in the same call before publishing; omit the define fields to publish an already-defined agent.
              properties:
                address:
                  type: string
                handle:
                  type: string
                  description: The registry-scoped name to list the agent under.
                display_name:
                  type: string
                  minLength: 1
                  maxLength: 120
                  description: Present to define the agent identity before publishing (define + publish in one call).
                endpoint_id:
                  type: string
                  format: uuid
                  description: Optional, only used when defining. Omit to resolve the endpoint from the address's routing.
                title:
                  type:
                    - string
                    - "null"
                  maxLength: 120
                description:
                  type:
                    - string
                    - "null"
                  maxLength: 2000
                tags:
                  type: array
                  items:
                    type: string
                    maxLength: 40
                  maxItems: 20
              required:
                - address
                - handle
              dependentRequired:
                endpoint_id:
                  - display_name
                title:
                  - display_name
                description:
                  - display_name
                tags:
                  - display_name
      responses:
        "200":
          description: Idempotent replay of an existing publication
          content:
            application/json:
              schema:
                allOf:
                  - type: object
                    properties:
                      success:
                        type: boolean
                        const: true
                    required:
                      - success
                      - data
                  - properties:
                      data:
                        type: object
                        properties:
                          status:
                            type: string
                            enum:
                              - approved
                              - requested
                            description: approved lists immediately; requested pends owner approval.
                          handle:
                            type: string
                          idempotent_replay:
                            type: boolean
                            description: True when the publish matched an existing identical publication.
                        required:
                          - status
                          - handle
                          - idempotent_replay
          headers:
            ratelimit-limit:
              description: Maximum number of requests allowed in the current window.
              schema:
                type: integer
                minimum: 1
                example: 120
            ratelimit-remaining:
              description: Remaining requests in the current window.
              schema:
                type: integer
                minimum: 0
                example: 118
            ratelimit-reset:
              description: Unix timestamp (seconds) when the current window resets.
              schema:
                type: integer
                example: 1700000060
            ratelimit-policy:
              description: Rate-limit policy in `limit;w=seconds` format, e.g. `120;w=60`.
              schema:
                type: string
                example: 120;w=60
        "201":
          description: Published (approved) or request created (requested)
          content:
            application/json:
              schema:
                allOf:
                  - type: object
                    properties:
                      success:
                        type: boolean
                        const: true
                    required:
                      - success
                      - data
                  - properties:
                      data:
                        type: object
                        properties:
                          status:
                            type: string
                            enum:
                              - approved
                              - requested
                            description: approved lists immediately; requested pends owner approval.
                          handle:
                            type: string
                          idempotent_replay:
                            type: boolean
                            description: True when the publish matched an existing identical publication.
                        required:
                          - status
                          - handle
                          - idempotent_replay
        "401":
          $ref: "#/components/responses/Unauthorized"
          description: Invalid or missing API key
        "403":
          $ref: "#/components/responses/Forbidden"
          description: Authenticated caller lacks permission for the operation
        "404":
          $ref: "#/components/responses/NotFound"
          description: Resource not found
        "409":
          $ref: "#/components/responses/Conflict"
          description: The request conflicts with the current state of the resource
        "422":
          $ref: "#/components/responses/UnprocessableEntity"
          description: Invalid request parameters
      security:
        - BearerAuth: []
      parameters:
        - name: Idempotency-Key
          in: header
          required: false
          description: Optional client-supplied idempotency key. Retrying a request with the same key returns the original result instead of performing the action a second time; if omitted the server derives one from the canonical payload hash. Safe to retry network failures without duplicating side effects.
          schema:
            type: string
            minLength: 1
            maxLength: 255
      description: Publish an agent into a registry
  /registries/{slug}/agents/{handle}:
    parameters:
      - name: slug
        in: path
        required: true
        schema:
          type: string
        description: The registry slug.
      - name: handle
        in: path
        required: true
        schema:
          type: string
        description: The registry-scoped handle the agent is published under.
    get:
      operationId: resolveRegistryHandle
      summary: Resolve a registry handle to its agent
      tags:
        - Registries
      security: []
      responses:
        "200":
          description: The agent listing under the handle
          content:
            application/json:
              schema:
                allOf:
                  - type: object
                    properties:
                      success:
                        type: boolean
                        const: true
                    required:
                      - success
                      - data
                  - properties:
                      data:
                        type: object
                        description: An agent's public directory profile.
                        properties:
                          address:
                            type: string
                          display_name:
                            type: string
                          title:
                            type:
                              - string
                              - "null"
                          description:
                            type:
                              - string
                              - "null"
                          tags:
                            type: array
                            items:
                              type: string
                          handle:
                            type:
                              - string
                              - "null"
                            description: The registry-scoped name. Null on the global by-address read.
                          last_reachable_at:
                            type:
                              - string
                              - "null"
                            format: date-time
                            description: When the agent's address was last confirmed to still route to its endpoint. A freshness signal for ranking listings; null until the first check.
                        required:
                          - address
                          - display_name
                          - title
                          - description
                          - tags
                          - handle
                          - last_reachable_at
        "404":
          $ref: "#/components/responses/NotFound"
          description: Resource not found
      description: Resolve a registry handle to its agent
    delete:
      operationId: unpublishAgent
      summary: Unpublish an agent from a registry
      tags:
        - Registries
      responses:
        "200":
          description: Resource deleted
          content:
            application/json:
              schema:
                allOf:
                  - type: object
                    properties:
                      success:
                        type: boolean
                        const: true
                    required:
                      - success
                      - data
                  - type: object
                    properties:
                      data:
                        type: object
                        properties:
                          deleted:
                            type: boolean
                            const: true
                        required:
                          - deleted
          headers:
            ratelimit-limit:
              description: Maximum number of requests allowed in the current window.
              schema:
                type: integer
                minimum: 1
                example: 120
            ratelimit-remaining:
              description: Remaining requests in the current window.
              schema:
                type: integer
                minimum: 0
                example: 118
            ratelimit-reset:
              description: Unix timestamp (seconds) when the current window resets.
              schema:
                type: integer
                example: 1700000060
            ratelimit-policy:
              description: Rate-limit policy in `limit;w=seconds` format, e.g. `120;w=60`.
              schema:
                type: string
                example: 120;w=60
        "401":
          $ref: "#/components/responses/Unauthorized"
          description: Invalid or missing API key
        "404":
          $ref: "#/components/responses/NotFound"
          description: Resource not found
      security:
        - BearerAuth: []
      description: Unpublish an agent from a registry
  /registries/{slug}/requests:
    parameters:
      - name: slug
        in: path
        required: true
        schema:
          type: string
        description: The registry slug.
    get:
      operationId: listRegistryRequests
      summary: List pending publication requests
      tags:
        - Registries
      parameters:
        - name: limit
          in: query
          required: false
          schema:
            type: integer
            minimum: 1
            maximum: 200
          description: Maximum number of items to return (1-200).
      responses:
        "200":
          description: Pending requests for a registry you own
          content:
            application/json:
              schema:
                allOf:
                  - type: object
                    properties:
                      success:
                        type: boolean
                        const: true
                    required:
                      - success
                      - data
                  - properties:
                      data:
                        type: array
                        items:
                          type: object
                          description: A pending publication request, as the registry owner sees it.
                          properties:
                            id:
                              type: string
                              format: uuid
                            address:
                              type: string
                            display_name:
                              type: string
                            handle:
                              type:
                                - string
                                - "null"
                            requested_at:
                              type: string
                              format: date-time
                          required:
                            - id
                            - address
                            - display_name
                            - handle
                            - requested_at
          headers:
            ratelimit-limit:
              description: Maximum number of requests allowed in the current window.
              schema:
                type: integer
                minimum: 1
                example: 120
            ratelimit-remaining:
              description: Remaining requests in the current window.
              schema:
                type: integer
                minimum: 0
                example: 118
            ratelimit-reset:
              description: Unix timestamp (seconds) when the current window resets.
              schema:
                type: integer
                example: 1700000060
            ratelimit-policy:
              description: Rate-limit policy in `limit;w=seconds` format, e.g. `120;w=60`.
              schema:
                type: string
                example: 120;w=60
        "401":
          $ref: "#/components/responses/Unauthorized"
          description: Invalid or missing API key
        "404":
          $ref: "#/components/responses/NotFound"
          description: Resource not found
      security:
        - BearerAuth: []
      description: List pending publication requests
  /registries/{slug}/requests/{id}:
    parameters:
      - name: slug
        in: path
        required: true
        schema:
          type: string
        description: The registry slug.
      - name: id
        in: path
        required: true
        schema:
          type: string
          format: uuid
        description: Resource UUID
    post:
      operationId: decideRegistryRequest
      summary: Approve or reject a publication request
      tags:
        - Registries
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                decision:
                  type: string
                  enum:
                    - approved
                    - rejected
              required:
                - decision
      responses:
        "200":
          description: Decision applied
          content:
            application/json:
              schema:
                allOf:
                  - type: object
                    properties:
                      success:
                        type: boolean
                        const: true
                    required:
                      - success
                      - data
                  - properties:
                      data:
                        type: object
                        properties:
                          status:
                            type: string
                            enum:
                              - approved
                              - rejected
                        required:
                          - status
          headers:
            ratelimit-limit:
              description: Maximum number of requests allowed in the current window.
              schema:
                type: integer
                minimum: 1
                example: 120
            ratelimit-remaining:
              description: Remaining requests in the current window.
              schema:
                type: integer
                minimum: 0
                example: 118
            ratelimit-reset:
              description: Unix timestamp (seconds) when the current window resets.
              schema:
                type: integer
                example: 1700000060
            ratelimit-policy:
              description: Rate-limit policy in `limit;w=seconds` format, e.g. `120;w=60`.
              schema:
                type: string
                example: 120;w=60
        "401":
          $ref: "#/components/responses/Unauthorized"
          description: Invalid or missing API key
        "404":
          $ref: "#/components/responses/NotFound"
          description: Resource not found
        "409":
          $ref: "#/components/responses/Conflict"
          description: The request conflicts with the current state of the resource
        "422":
          $ref: "#/components/responses/UnprocessableEntity"
          description: Invalid request parameters
      security:
        - BearerAuth: []
      parameters:
        - name: Idempotency-Key
          in: header
          required: false
          description: Optional client-supplied idempotency key. Retrying a request with the same key returns the original result instead of performing the action a second time; if omitted the server derives one from the canonical payload hash. Safe to retry network failures without duplicating side effects.
          schema:
            type: string
            minLength: 1
            maxLength: 255
      description: Approve or reject a publication request
  /agents:
    post:
      operationId: defineAgent
      summary: Define an agent identity
      tags:
        - Registries
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                address:
                  type: string
                  description: The agent's globally unique email address; mail to it must route to an endpoint the account controls.
                endpoint_id:
                  type: string
                  format: uuid
                  description: Optional. The endpoint the agent runs on. Omit it to resolve the endpoint from the address's routing automatically; supply it to pin a specific endpoint, which is then validated against the address's route.
                display_name:
                  type: string
                  minLength: 1
                  maxLength: 120
                title:
                  type:
                    - string
                    - "null"
                  maxLength: 120
                description:
                  type:
                    - string
                    - "null"
                  maxLength: 2000
                tags:
                  type: array
                  items:
                    type: string
                    maxLength: 40
                  maxItems: 20
              required:
                - address
                - display_name
      responses:
        "201":
          description: Agent defined
          content:
            application/json:
              schema:
                allOf:
                  - type: object
                    properties:
                      success:
                        type: boolean
                        const: true
                    required:
                      - success
                      - data
                  - properties:
                      data:
                        type: object
                        properties:
                          id:
                            type: string
                            format: uuid
                          address:
                            type: string
                        required:
                          - id
                          - address
        "401":
          $ref: "#/components/responses/Unauthorized"
          description: Invalid or missing API key
        "409":
          $ref: "#/components/responses/Conflict"
          description: The request conflicts with the current state of the resource
        "422":
          $ref: "#/components/responses/UnprocessableEntity"
          description: Invalid request parameters
      security:
        - BearerAuth: []
      parameters:
        - name: Idempotency-Key
          in: header
          required: false
          description: Optional client-supplied idempotency key. Retrying a request with the same key returns the original result instead of performing the action a second time; if omitted the server derives one from the canonical payload hash. Safe to retry network failures without duplicating side effects.
          schema:
            type: string
            minLength: 1
            maxLength: 255
      description: Define an agent identity
  /agents/{address}:
    parameters:
      - name: address
        in: path
        required: true
        schema:
          type: string
        description: The agent's email address (URL-encoded).
    get:
      operationId: getAgent
      summary: Get an agent's public profile by address
      tags:
        - Registries
      security: []
      responses:
        "200":
          description: The agent's public profile
          content:
            application/json:
              schema:
                allOf:
                  - type: object
                    properties:
                      success:
                        type: boolean
                        const: true
                    required:
                      - success
                      - data
                  - properties:
                      data:
                        type: object
                        description: An agent's public directory profile.
                        properties:
                          address:
                            type: string
                          display_name:
                            type: string
                          title:
                            type:
                              - string
                              - "null"
                          description:
                            type:
                              - string
                              - "null"
                          tags:
                            type: array
                            items:
                              type: string
                          handle:
                            type:
                              - string
                              - "null"
                            description: The registry-scoped name. Null on the global by-address read.
                          last_reachable_at:
                            type:
                              - string
                              - "null"
                            format: date-time
                            description: When the agent's address was last confirmed to still route to its endpoint. A freshness signal for ranking listings; null until the first check.
                        required:
                          - address
                          - display_name
                          - title
                          - description
                          - tags
                          - handle
                          - last_reachable_at
        "404":
          $ref: "#/components/responses/NotFound"
          description: Resource not found
      description: Get an agent's public profile by address
  /templates:
    get:
      operationId: listTemplates
      summary: List function templates
      description: List approved Function templates available for browsing and installation. Results are cacheable and paginated with `data.next_cursor`.
      tags:
        - Templates
      security: []
      parameters:
        - name: q
          in: query
          required: false
          description: Search templates by title, summary, or slug.
          schema:
            type: string
            minLength: 1
        - name: tag
          in: query
          required: false
          description: Filter templates by an exact tag.
          schema:
            type: string
            minLength: 1
        - name: cursor
          in: query
          required: false
          description: Cursor from a previous response `data.next_cursor` value.
          schema:
            type: string
        - name: limit
          in: query
          required: false
          description: Maximum number of templates to return. The server caps this at 100.
          schema:
            type: integer
            minimum: 1
            maximum: 100
            default: 50
      responses:
        "200":
          description: Paginated template registry summaries.
          content:
            application/json:
              schema:
                allOf:
                  - type: object
                    properties:
                      success:
                        type: boolean
                        const: true
                    required:
                      - success
                      - data
                  - type: object
                    properties:
                      data:
                        type: object
                        properties:
                          items:
                            type: array
                            items:
                              type: object
                              properties:
                                id:
                                  type: string
                                  format: uuid
                                slug:
                                  type: string
                                  pattern: ^[a-z0-9][a-z0-9_-]{0,62}$
                                  description: Stable template slug used in template URLs and install commands.
                                title:
                                  type: string
                                summary:
                                  type: string
                                author:
                                  type: object
                                  additionalProperties: false
                                  properties:
                                    id:
                                      type: string
                                      minLength: 1
                                    name:
                                      type: string
                                      minLength: 1
                                    url:
                                      type: string
                                      format: uri
                                  required:
                                    - id
                                    - name
                                tags:
                                  type: array
                                  items:
                                    type: string
                                verified:
                                  type: boolean
                                install_count:
                                  type: integer
                                  minimum: 0
                                github_repo:
                                  type: string
                                  pattern: ^[A-Za-z0-9-]+/[A-Za-z0-9_.-]+$
                                  description: GitHub repository in owner/repo form.
                                created_at:
                                  type: string
                                  format: date-time
                                updated_at:
                                  type: string
                                  format: date-time
                              required:
                                - id
                                - slug
                                - title
                                - summary
                                - author
                                - tags
                                - verified
                                - install_count
                                - github_repo
                                - created_at
                                - updated_at
                          next_cursor:
                            type:
                              - string
                              - "null"
                            description: Cursor to pass as the next `cursor` query value, or null when there are no more templates.
                        required:
                          - items
                          - next_cursor
        "400":
          $ref: "#/components/responses/ValidationError"
          description: Invalid request parameters
        "429":
          $ref: "#/components/responses/RateLimited"
          description: Rate limit exceeded
  /templates/{id}:
    get:
      operationId: getTemplate
      summary: Get a function template
      description: Fetch one approved Function template by slug, including its manifest snapshot and README. The stored source files used for install are not returned.
      tags:
        - Templates
      security: []
      parameters:
        - name: id
          in: path
          required: true
          description: Template slug from the template manifest.
          schema:
            type: string
            pattern: ^[a-z0-9][a-z0-9_-]{0,62}$
            description: Template slug.
      responses:
        "200":
          description: Template registry detail.
          content:
            application/json:
              schema:
                allOf:
                  - type: object
                    properties:
                      success:
                        type: boolean
                        const: true
                    required:
                      - success
                      - data
                  - type: object
                    properties:
                      data:
                        type: object
                        properties:
                          id:
                            type: string
                            format: uuid
                          slug:
                            type: string
                            pattern: ^[a-z0-9][a-z0-9_-]{0,62}$
                            description: Stable template slug used in template URLs and install commands.
                          title:
                            type: string
                          summary:
                            type: string
                          author:
                            type: object
                            additionalProperties: false
                            properties:
                              id:
                                type: string
                                minLength: 1
                              name:
                                type: string
                                minLength: 1
                              url:
                                type: string
                                format: uri
                            required:
                              - id
                              - name
                          tags:
                            type: array
                            items:
                              type: string
                          verified:
                            type: boolean
                          install_count:
                            type: integer
                            minimum: 0
                          github_repo:
                            type: string
                            pattern: ^[A-Za-z0-9-]+/[A-Za-z0-9_.-]+$
                            description: GitHub repository in owner/repo form.
                          created_at:
                            type: string
                            format: date-time
                          updated_at:
                            type: string
                            format: date-time
                          description:
                            type:
                              - string
                              - "null"
                          github_sha:
                            type: string
                          github_path:
                            type:
                              - string
                              - "null"
                          manifest:
                            type: object
                            additionalProperties: false
                            properties:
                              schemaVersion:
                                type: integer
                                const: 1
                              id:
                                type: string
                                pattern: ^[a-z0-9][a-z0-9_-]{0,62}$
                                description: Stable template slug from the manifest.
                              title:
                                type: string
                                minLength: 1
                              summary:
                                type: string
                                minLength: 1
                              description:
                                type: string
                              author:
                                type: object
                                additionalProperties: false
                                properties:
                                  id:
                                    type: string
                                    minLength: 1
                                  name:
                                    type: string
                                    minLength: 1
                                  url:
                                    type: string
                                    format: uri
                                required:
                                  - id
                                  - name
                              tags:
                                type: array
                                items:
                                  type: string
                                  minLength: 1
                                default: []
                              source:
                                oneOf:
                                  - type: object
                                    additionalProperties: false
                                    properties:
                                      mode:
                                        type: string
                                        const: managed-build
                                      dir:
                                        type: string
                                        minLength: 1
                                        default: .
                                    required:
                                      - mode
                                      - dir
                                  - type: object
                                    additionalProperties: false
                                    properties:
                                      mode:
                                        type: string
                                        const: bundle
                                      file:
                                        type: string
                                        minLength: 1
                                    required:
                                      - mode
                                      - file
                              install:
                                type: object
                                additionalProperties: false
                                properties:
                                  mode:
                                    type: string
                                    enum:
                                      - deploy
                                      - scaffold
                                  editFiles:
                                    type: array
                                    items:
                                      type: string
                                      minLength: 1
                                    default: []
                                  reason:
                                    type: string
                                    default: ""
                                required:
                                  - mode
                                  - editFiles
                                  - reason
                              secrets:
                                type: array
                                items:
                                  type: object
                                  additionalProperties: false
                                  properties:
                                    key:
                                      type: string
                                      pattern: ^[A-Z_][A-Z0-9_]*$
                                    required:
                                      type: boolean
                                      default: true
                                    description:
                                      type: string
                                  required:
                                    - key
                                    - required
                                default: []
                              secretGroups:
                                type: array
                                items:
                                  type: object
                                  additionalProperties: false
                                  properties:
                                    keys:
                                      type: array
                                      minItems: 2
                                      items:
                                        type: string
                                        pattern: ^[A-Z_][A-Z0-9_]*$
                                    min:
                                      type: integer
                                      minimum: 1
                                      default: 1
                                    description:
                                      type: string
                                  required:
                                    - keys
                                    - min
                                default: []
                              variables:
                                type: array
                                items:
                                  type: object
                                  additionalProperties: false
                                  properties:
                                    key:
                                      type: string
                                      minLength: 1
                                    prompt:
                                      type: string
                                      minLength: 1
                                    default:
                                      type: string
                                    file:
                                      type: string
                                      minLength: 1
                                    type:
                                      type: string
                                      enum:
                                        - string
                                        - select
                                        - url
                                        - email
                                      default: string
                                    options:
                                      type: array
                                      items:
                                        type: string
                                        minLength: 1
                                    validation:
                                      type: object
                                      additionalProperties: false
                                      properties:
                                        pattern:
                                          type: string
                                          minLength: 1
                                        maxLength:
                                          type: integer
                                          minimum: 1
                                  required:
                                    - key
                                    - prompt
                                    - type
                                default: []
                              consumesVendors:
                                type: array
                                items:
                                  type: object
                                  additionalProperties: false
                                  properties:
                                    slug:
                                      type: string
                                      minLength: 1
                                    required:
                                      type: boolean
                                      default: true
                                  required:
                                    - slug
                                default: []
                              setup:
                                type: object
                                additionalProperties: false
                                properties:
                                  agent:
                                    type: string
                                    minLength: 1
                                  prompt:
                                    type: string
                                    minLength: 1
                                  produces:
                                    type: array
                                    items:
                                      type: string
                                      minLength: 1
                                    default: []
                                required:
                                  - agent
                                  - prompt
                                  - produces
                              postInstall:
                                type: string
                            required:
                              - schemaVersion
                              - id
                              - title
                              - summary
                              - author
                              - tags
                              - source
                              - install
                              - secrets
                              - secretGroups
                              - variables
                          readme:
                            type:
                              - string
                              - "null"
                          status:
                            type: string
                            enum:
                              - pending
                              - approved
                              - rejected
                        required:
                          - id
                          - slug
                          - title
                          - summary
                          - author
                          - tags
                          - verified
                          - install_count
                          - github_repo
                          - created_at
                          - updated_at
                          - description
                          - github_sha
                          - github_path
                          - manifest
                          - readme
                          - status
        "400":
          $ref: "#/components/responses/ValidationError"
          description: Invalid request parameters
        "404":
          $ref: "#/components/responses/NotFound"
          description: Resource not found
        "429":
          $ref: "#/components/responses/RateLimited"
          description: Rate limit exceeded
  /send-mail/demo:
    post:
      operationId: sendMailDemo
      summary: Try send-mail without authentication (simulation)
      description: "Unauthenticated sandbox for `POST /send-mail`. Accepts the same request body, validates it identically, and returns a realistic synthetic success envelope with `demo: true`. **No email is sent, queued, or stored.** Use this to exercise the send flow without credentials; to send real mail, sign up at https://primitive.dev/signup for an API key and call `POST /send-mail` with `Authorization: Bearer prim_<key>`."
      tags:
        - Sending
      security: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              additionalProperties: false
              properties:
                from:
                  type: string
                  minLength: 3
                  maxLength: 998
                  description: RFC 5322 From header. The sender domain must be a verified outbound domain for your organization.
                to:
                  type: string
                  minLength: 3
                  maxLength: 320
                  description: Recipient address. Recipient eligibility depends on your account's outbound entitlements.
                subject:
                  type: string
                  minLength: 1
                  maxLength: 998
                  description: Subject line for the outbound message
                body_text:
                  type: string
                  description: Plain-text message body. At least one of body_text or body_html is required. The combined UTF-8 byte length of body_text and body_html must be at most 262144 bytes.
                body_html:
                  type: string
                  description: HTML message body. At least one of body_text or body_html is required. The combined UTF-8 byte length of body_text and body_html must be at most 262144 bytes.
                in_reply_to:
                  type: string
                  minLength: 1
                  maxLength: 998
                  pattern: ^[^\x00-\x1F\x7F]+$
                  description: Message-ID of the direct parent email when sending a threaded reply.
                references:
                  type: array
                  maxItems: 100
                  description: Full ordered message-id chain for the thread.
                  items:
                    type: string
                    minLength: 1
                    maxLength: 998
                    pattern: ^[^\x00-\x1F\x7F]+$
                attachments:
                  type: array
                  maxItems: 100
                  description: Inline attachments. Send requests with attachments to https://api.primitive.dev/v1/send-mail. Combined raw decoded attachment bytes must be at most 31457280.
                  items:
                    type: object
                    additionalProperties: false
                    properties:
                      filename:
                        type: string
                        minLength: 1
                        maxLength: 255
                        description: Attachment filename. Control characters are rejected.
                      content_type:
                        type: string
                        minLength: 1
                        maxLength: 255
                        description: Optional MIME content type. Control characters are rejected.
                      content_base64:
                        type: string
                        minLength: 1
                        maxLength: 44040192
                        description: Base64-encoded attachment bytes.
                    required:
                      - filename
                      - content_base64
                wait:
                  type: boolean
                  description: When true, wait for the first downstream SMTP delivery outcome before returning.
                wait_timeout_ms:
                  type: integer
                  minimum: 1000
                  maximum: 30000
                  description: Maximum time to wait for a delivery outcome when wait is true. Defaults to 30000.
              required:
                - from
                - to
                - subject
            example:
              from: agent@your-subdomain.primitive.email
              to: someone@example.com
              subject: Hello from my agent
              body_text: This is a test send via the public demo endpoint.
      responses:
        "200":
          description: Simulated send accepted. Identical shape to a fresh asynchronous /send-mail response, plus a top-level `demo` flag. No mail was sent.
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    const: true
                  demo:
                    type: boolean
                    const: true
                  note:
                    type: string
                    description: Human-readable reminder that the response is simulated.
                  data:
                    type: object
                    properties:
                      id:
                        type: string
                        description: Persisted sent-email attempt ID.
                      status:
                        type: string
                        description: |
                          Lifecycle status of a sent_emails row. Possible values:

                            - `queued`: pre-call INSERT; the outbound agent has not
                              yet replied.
                            - `submitted_to_agent`: agent accepted; `queue_id` is set.
                            - `agent_failed`: agent rejected; `error_code` and
                              `error_message` carry the reason.
                            - `gate_denied`: a recipient-scope gate denied the send;
                              the agent was never called. The `gates` array carries
                              the denial detail. /send-mail returns 403 in this case
                              so callers see the denial synchronously; /sent-emails
                              additionally records the row for historical lookup,
                              which is when this status appears in a listing.
                            - `unknown`: terminal indeterminate; the on-box log
                              poller couldn't classify the receiver's response.
                            - `delivered` / `bounced` / `deferred` / `wait_timeout`:
                              terminal delivery outcomes (see DeliveryStatus).
                        enum:
                          - queued
                          - submitted_to_agent
                          - agent_failed
                          - gate_denied
                          - unknown
                          - delivered
                          - bounced
                          - deferred
                          - wait_timeout
                      from:
                        type: string
                        description: |
                          Bare from-address actually written on the wire. Echoed
                          on every success branch so callers can confirm what
                          went out, particularly useful for the /emails/{id}/reply
                          path where `from` is server-derived from the inbound's
                          recipient when the caller doesn't override.

                          For sends where the caller passed a from-header that
                          included a display name (e.g. `"Acme Support" <support@acme.test>`),
                          this field is the parsed bare address (`support@acme.test`).
                          The display name was sent on the wire intact; this field
                          just makes the address easy to compare against allowlists.
                      queue_id:
                        type:
                          - string
                          - "null"
                        description: |
                          Message identifier assigned by Primitive's OUTBOUND relay
                          (the box that signs your mail and submits it to the
                          receiving MTA). NOT the receiver's queue id.

                          The receiver may also report its own queue id in
                          `smtp_response_text` (e.g. `"250 2.0.0 Ok: queued as
                          99D111927CDA"` from a Postfix receiver). Those two ids
                          refer to different mail systems and are NOT comparable.
                          Treat `queue_id` as Primitive-internal and the
                          receiver's id as remote-system-internal.

                          Null on rows that never reached the relay (queued,
                          gate_denied, agent_failed before signing).
                      accepted:
                        type: array
                        items:
                          type: string
                        description: Recipient addresses accepted by the relay.
                      rejected:
                        type: array
                        items:
                          type: string
                        description: Recipient addresses rejected by the relay.
                      client_idempotency_key:
                        type: string
                        description: Effective idempotency key used for this send.
                      request_id:
                        type: string
                        description: Server-issued request identifier for support and tracing.
                      content_hash:
                        type: string
                        description: Stable hash of the canonical send payload.
                      delivery_status:
                        type: string
                        description: |
                          Narrower enum covering only the four terminal delivery
                          outcomes returned to a synchronous `wait: true` send.

                          On the SendMailResult shape, `delivery_status` is always
                          equal to `status` whenever both are present (i.e. on
                          terminal-state replays and live wait=true responses).
                          The two fields exist so callers that want to type-narrow
                          on "this is a delivery outcome" can pattern-match against
                          the four-value enum without handling the broader
                          SentEmailStatus value set (which also covers `queued`,
                          `submitted_to_agent`, `agent_failed`, `gate_denied`,
                          `unknown`).

                          On async-mode and pre-terminal responses, `delivery_status`
                          is absent and only `status` is populated. Use `status` if
                          you want a single field that's always present.
                        enum:
                          - delivered
                          - bounced
                          - deferred
                          - wait_timeout
                      smtp_response_code:
                        type:
                          - integer
                          - "null"
                        description: SMTP response code from the first downstream delivery outcome when wait is true.
                      smtp_response_text:
                        type: string
                        description: SMTP response text from the first downstream delivery outcome when wait is true.
                      idempotent_replay:
                        type: boolean
                        description: |
                          True when the response replays a previously-recorded send
                          keyed by `client_idempotency_key` (same key, same canonical
                          payload). False on a fresh send and on gate-denied
                          responses. Lets callers branch on cache state without
                          diffing fields.
                    required:
                      - id
                      - status
                      - from
                      - queue_id
                      - accepted
                      - rejected
                      - client_idempotency_key
                      - request_id
                      - content_hash
                      - idempotent_replay
                required:
                  - success
                  - demo
                  - data
              example:
                success: true
                demo: true
                note: "Simulated response. No email was sent. Sign up at https://primitive.dev/signup for an API key, then POST the same body to /v1/send-mail with Authorization: Bearer prim_<key>."
                data:
                  id: 3f1c0a9e-2b7d-4e5a-9c6f-8d2e1a4b5c6d
                  status: queued
                  from: agent@your-subdomain.primitive.email
                  queue_id: demo_9a3f1c0a
                  accepted:
                    - someone@example.com
                  rejected: []
                  client_idempotency_key: null
                  request_id: 7b2e4d6a-1c3f-4a5b-8d9e-0f1a2b3c4d5e
                  content_hash: a1b2c3d4e5f60718293a4b5c6d7e8f90a1b2c3d4e5f60718293a4b5c6d7e8f90
                  idempotent_replay: false
                  dedup_reason: null
        "400":
          $ref: "#/components/responses/ValidationError"
          description: Invalid request parameters
        "413":
          $ref: "#/components/responses/PayloadTooLarge"
          description: Request body exceeds the demo size cap.
        "415":
          $ref: "#/components/responses/UnsupportedMediaType"
          description: Content-Type must be application/json.
        "429":
          $ref: "#/components/responses/RateLimited"
          description: Demo rate limit reached.
      parameters:
        - name: Idempotency-Key
          in: header
          required: false
          description: Optional client-supplied idempotency key. Retrying a request with the same key returns the original result instead of performing the action a second time; if omitted the server derives one from the canonical payload hash. Safe to retry network failures without duplicating side effects.
          schema:
            type: string
            minLength: 1
            maxLength: 255
  /send-mail/batch:
    post:
      operationId: sendMailBatch
      summary: Send a batch of emails in one request
      description: "Send up to 100 independent messages in a single call. Each message uses the same body schema and the same send path as `POST /send-mail`, so auth, validation, rate limiting, and idempotency apply per message. Messages are processed independently: one failing does not fail the others — every message gets an entry in `results` carrying either `data` (on success) or `error` (on failure), with the original `index`."
      tags:
        - Sending
      security:
        - BearerAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              additionalProperties: false
              required:
                - messages
              properties:
                messages:
                  type: array
                  minItems: 1
                  maxItems: 100
                  description: The messages to send. Each item is a standard send-mail request body.
                  items:
                    type: object
                    additionalProperties: false
                    properties:
                      from:
                        type: string
                        minLength: 3
                        maxLength: 998
                        description: RFC 5322 From header. The sender domain must be a verified outbound domain for your organization.
                      to:
                        type: string
                        minLength: 3
                        maxLength: 320
                        description: Recipient address. Recipient eligibility depends on your account's outbound entitlements.
                      subject:
                        type: string
                        minLength: 1
                        maxLength: 998
                        description: Subject line for the outbound message
                      body_text:
                        type: string
                        description: Plain-text message body. At least one of body_text or body_html is required. The combined UTF-8 byte length of body_text and body_html must be at most 262144 bytes.
                      body_html:
                        type: string
                        description: HTML message body. At least one of body_text or body_html is required. The combined UTF-8 byte length of body_text and body_html must be at most 262144 bytes.
                      in_reply_to:
                        type: string
                        minLength: 1
                        maxLength: 998
                        pattern: ^[^\x00-\x1F\x7F]+$
                        description: Message-ID of the direct parent email when sending a threaded reply.
                      references:
                        type: array
                        maxItems: 100
                        description: Full ordered message-id chain for the thread.
                        items:
                          type: string
                          minLength: 1
                          maxLength: 998
                          pattern: ^[^\x00-\x1F\x7F]+$
                      attachments:
                        type: array
                        maxItems: 100
                        description: Inline attachments. Send requests with attachments to https://api.primitive.dev/v1/send-mail. Combined raw decoded attachment bytes must be at most 31457280.
                        items:
                          type: object
                          additionalProperties: false
                          properties:
                            filename:
                              type: string
                              minLength: 1
                              maxLength: 255
                              description: Attachment filename. Control characters are rejected.
                            content_type:
                              type: string
                              minLength: 1
                              maxLength: 255
                              description: Optional MIME content type. Control characters are rejected.
                            content_base64:
                              type: string
                              minLength: 1
                              maxLength: 44040192
                              description: Base64-encoded attachment bytes.
                          required:
                            - filename
                            - content_base64
                      wait:
                        type: boolean
                        description: When true, wait for the first downstream SMTP delivery outcome before returning.
                      wait_timeout_ms:
                        type: integer
                        minimum: 1000
                        maximum: 30000
                        description: Maximum time to wait for a delivery outcome when wait is true. Defaults to 30000.
                    required:
                      - from
                      - to
                      - subject
            example:
              messages:
                - from: agent@your-subdomain.primitive.email
                  to: a@example.com
                  subject: Hi A
                  body_text: Hello A
                - from: agent@your-subdomain.primitive.email
                  to: b@example.com
                  subject: Hi B
                  body_text: Hello B
      responses:
        "200":
          description: Per-message results. The request as a whole returns HTTP 200 even when individual messages fail; inspect each `results` entry.
          content:
            application/json:
              schema:
                type: object
                required:
                  - success
                  - data
                properties:
                  success:
                    type: boolean
                  data:
                    type: object
                    required:
                      - count
                      - results
                    properties:
                      count:
                        type: integer
                        description: Number of messages in the batch.
                      results:
                        type: array
                        items:
                          type: object
                          required:
                            - index
                            - success
                          properties:
                            index:
                              type: integer
                              description: Zero-based position of this message in the request array.
                            success:
                              type: boolean
                            data:
                              type: object
                              properties:
                                id:
                                  type: string
                                  description: Persisted sent-email attempt ID.
                                status:
                                  type: string
                                  description: |
                                    Lifecycle status of a sent_emails row. Possible values:

                                      - `queued`: pre-call INSERT; the outbound agent has not
                                        yet replied.
                                      - `submitted_to_agent`: agent accepted; `queue_id` is set.
                                      - `agent_failed`: agent rejected; `error_code` and
                                        `error_message` carry the reason.
                                      - `gate_denied`: a recipient-scope gate denied the send;
                                        the agent was never called. The `gates` array carries
                                        the denial detail. /send-mail returns 403 in this case
                                        so callers see the denial synchronously; /sent-emails
                                        additionally records the row for historical lookup,
                                        which is when this status appears in a listing.
                                      - `unknown`: terminal indeterminate; the on-box log
                                        poller couldn't classify the receiver's response.
                                      - `delivered` / `bounced` / `deferred` / `wait_timeout`:
                                        terminal delivery outcomes (see DeliveryStatus).
                                  enum:
                                    - queued
                                    - submitted_to_agent
                                    - agent_failed
                                    - gate_denied
                                    - unknown
                                    - delivered
                                    - bounced
                                    - deferred
                                    - wait_timeout
                                from:
                                  type: string
                                  description: |
                                    Bare from-address actually written on the wire. Echoed
                                    on every success branch so callers can confirm what
                                    went out, particularly useful for the /emails/{id}/reply
                                    path where `from` is server-derived from the inbound's
                                    recipient when the caller doesn't override.

                                    For sends where the caller passed a from-header that
                                    included a display name (e.g. `"Acme Support" <support@acme.test>`),
                                    this field is the parsed bare address (`support@acme.test`).
                                    The display name was sent on the wire intact; this field
                                    just makes the address easy to compare against allowlists.
                                queue_id:
                                  type:
                                    - string
                                    - "null"
                                  description: |
                                    Message identifier assigned by Primitive's OUTBOUND relay
                                    (the box that signs your mail and submits it to the
                                    receiving MTA). NOT the receiver's queue id.

                                    The receiver may also report its own queue id in
                                    `smtp_response_text` (e.g. `"250 2.0.0 Ok: queued as
                                    99D111927CDA"` from a Postfix receiver). Those two ids
                                    refer to different mail systems and are NOT comparable.
                                    Treat `queue_id` as Primitive-internal and the
                                    receiver's id as remote-system-internal.

                                    Null on rows that never reached the relay (queued,
                                    gate_denied, agent_failed before signing).
                                accepted:
                                  type: array
                                  items:
                                    type: string
                                  description: Recipient addresses accepted by the relay.
                                rejected:
                                  type: array
                                  items:
                                    type: string
                                  description: Recipient addresses rejected by the relay.
                                client_idempotency_key:
                                  type: string
                                  description: Effective idempotency key used for this send.
                                request_id:
                                  type: string
                                  description: Server-issued request identifier for support and tracing.
                                content_hash:
                                  type: string
                                  description: Stable hash of the canonical send payload.
                                delivery_status:
                                  type: string
                                  description: |
                                    Narrower enum covering only the four terminal delivery
                                    outcomes returned to a synchronous `wait: true` send.

                                    On the SendMailResult shape, `delivery_status` is always
                                    equal to `status` whenever both are present (i.e. on
                                    terminal-state replays and live wait=true responses).
                                    The two fields exist so callers that want to type-narrow
                                    on "this is a delivery outcome" can pattern-match against
                                    the four-value enum without handling the broader
                                    SentEmailStatus value set (which also covers `queued`,
                                    `submitted_to_agent`, `agent_failed`, `gate_denied`,
                                    `unknown`).

                                    On async-mode and pre-terminal responses, `delivery_status`
                                    is absent and only `status` is populated. Use `status` if
                                    you want a single field that's always present.
                                  enum:
                                    - delivered
                                    - bounced
                                    - deferred
                                    - wait_timeout
                                smtp_response_code:
                                  type:
                                    - integer
                                    - "null"
                                  description: SMTP response code from the first downstream delivery outcome when wait is true.
                                smtp_response_text:
                                  type: string
                                  description: SMTP response text from the first downstream delivery outcome when wait is true.
                                idempotent_replay:
                                  type: boolean
                                  description: |
                                    True when the response replays a previously-recorded send
                                    keyed by `client_idempotency_key` (same key, same canonical
                                    payload). False on a fresh send and on gate-denied
                                    responses. Lets callers branch on cache state without
                                    diffing fields.
                              required:
                                - id
                                - status
                                - from
                                - queue_id
                                - accepted
                                - rejected
                                - client_idempotency_key
                                - request_id
                                - content_hash
                                - idempotent_replay
                            error:
                              type: object
                              required:
                                - code
                                - message
                              properties:
                                code:
                                  type: string
                                message:
                                  type: string
          headers:
            ratelimit-limit:
              description: Maximum number of requests allowed in the current window.
              schema:
                type: integer
                minimum: 1
                example: 120
            ratelimit-remaining:
              description: Remaining requests in the current window.
              schema:
                type: integer
                minimum: 0
                example: 118
            ratelimit-reset:
              description: Unix timestamp (seconds) when the current window resets.
              schema:
                type: integer
                example: 1700000060
            ratelimit-policy:
              description: Rate-limit policy in `limit;w=seconds` format, e.g. `120;w=60`.
              schema:
                type: string
                example: 120;w=60
        "400":
          $ref: "#/components/responses/ValidationError"
          description: Invalid request parameters
        "401":
          $ref: "#/components/responses/Unauthorized"
          description: Invalid or missing API key
        "429":
          $ref: "#/components/responses/RateLimited"
          description: Rate limit exceeded
      parameters:
        - name: Idempotency-Key
          in: header
          required: false
          description: Optional client-supplied idempotency key. Retrying a request with the same key returns the original result instead of performing the action a second time; if omitted the server derives one from the canonical payload hash. Safe to retry network failures without duplicating side effects.
          schema:
            type: string
            minLength: 1
            maxLength: 255
  /demo/jobs:
    post:
      operationId: startDemoJob
      summary: Start a sample asynchronous job without authentication
      description: Public, no-auth demo of the asynchronous-job pattern. Starts a synthetic job and returns `202 Accepted` with a `Location` header (and `data.status_url`) pointing at the job-status resource. Poll `GET /demo/jobs/{id}` until the status is terminal. No real work runs; the status is a fixed sample.
      tags:
        - Demo
      security: []
      responses:
        "202":
          description: Job accepted. Poll the returned status URL for progress.
          headers:
            Location:
              schema:
                type: string
                format: uri
              description: URL of the job-status resource to poll.
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    const: true
                  demo:
                    type: boolean
                    const: true
                  note:
                    type: string
                  data:
                    type: object
                    properties:
                      id:
                        type: string
                      type:
                        type: string
                      status:
                        type: string
                        enum:
                          - queued
                          - processing
                          - completed
                          - failed
                      status_url:
                        type: string
                        format: uri
                      created_at:
                        type: string
                        format: date-time
                    required:
                      - id
                      - status
                      - status_url
                required:
                  - success
                  - demo
                  - data
        "429":
          $ref: "#/components/responses/RateLimited"
          description: Demo rate limit reached.
      parameters:
        - name: Idempotency-Key
          in: header
          required: false
          description: Optional client-supplied idempotency key. Retrying a request with the same key returns the original result instead of performing the action a second time; if omitted the server derives one from the canonical payload hash. Safe to retry network failures without duplicating side effects.
          schema:
            type: string
            minLength: 1
            maxLength: 255
  /demo/jobs/{id}:
    get:
      operationId: getDemoJob
      summary: Poll a sample asynchronous job without authentication
      description: Public, no-auth demo. Returns the status of a synthetic job started by `POST /demo/jobs` — the poll half of the async-job pattern. No real work runs; the status is a fixed sample.
      tags:
        - Demo
      security: []
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
          description: Job id returned by POST /demo/jobs.
      responses:
        "200":
          description: Current job status.
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    const: true
                  demo:
                    type: boolean
                    const: true
                  note:
                    type: string
                  data:
                    type: object
                    properties:
                      id:
                        type: string
                      type:
                        type: string
                      status:
                        type: string
                        enum:
                          - queued
                          - processing
                          - completed
                          - failed
                      progress:
                        type: integer
                      created_at:
                        type: string
                        format: date-time
                      estimated_completion_at:
                        type: string
                        format: date-time
                    required:
                      - id
                      - status
                      - progress
                required:
                  - success
                  - demo
                  - data
        "429":
          $ref: "#/components/responses/RateLimited"
          description: Demo rate limit reached.
  /demo/emails:
    get:
      operationId: getDemoEmails
      summary: List sample emails without authentication
      description: Public, no-auth demo. Returns synthetic email objects shaped exactly like the authenticated `GET /emails` response (same `EmailSummary` schema), so an arriving agent can read the data model before obtaining credentials. No real tenant data is exposed; every object is a fixed sample. Rate-limited per IP.
      tags:
        - Demo
      security: []
      responses:
        "200":
          description: Synthetic list of emails, same shape as the authenticated /emails response.
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    const: true
                  demo:
                    type: boolean
                    const: true
                  note:
                    type: string
                    description: Reminder that the data is synthetic.
                  data:
                    type: array
                    items:
                      type: object
                      properties:
                        id:
                          type: string
                          format: uuid
                        message_id:
                          type:
                            - string
                            - "null"
                        domain_id:
                          type:
                            - string
                            - "null"
                          format: uuid
                        org_id:
                          type:
                            - string
                            - "null"
                          format: uuid
                        status:
                          type: string
                          description: |
                            Lifecycle status of an INBOUND email (a row in the `emails`
                            table). Distinct from `SentEmailStatus`, which describes
                            the OUTBOUND lifecycle (the `sent_emails` table) and uses
                            a different vocabulary because the lifecycles differ.
                            Possible values:

                              - `pending`: the row was inserted at ingestion (mx_main)
                                and has not yet completed the spam / filter / auth
                                pipeline. Body and parsed fields are present; webhook
                                delivery is not yet scheduled. Most rows transition out
                                of `pending` within seconds.
                              - `accepted`: the inbound passed the policy gates and is
                                queued for webhook delivery. The `webhook_status` field
                                tracks the separate webhook-delivery lifecycle from
                                this point.
                              - `completed`: terminal success. Webhook delivery
                                attempted and acknowledged by every active endpoint, OR
                                no endpoints are configured, so the row is durably
                                archived.
                              - `rejected`: terminal failure at ingestion (spam, blocked
                                sender, filter rule, malformed). The body and metadata
                                are stored for auditing but no webhook fires and the
                                row is not repliable.

                            See also `webhook_status` (separate enum tracking the
                            webhook-delivery state machine) and `SentEmailStatus` (the
                            outbound vocabulary).
                          enum:
                            - pending
                            - accepted
                            - completed
                            - rejected
                        sender:
                          type: string
                          description: |
                            SMTP envelope sender (return-path) the inbound mail server
                            accepted. For most legitimate mail this equals the bare
                            address in the From header; for mailing lists, bounce
                            handlers, and forwarders it is typically the bounce address
                            rather than the human-visible sender.

                            For the parsed From-header value (with display name handling
                            and a sender-fallback when the header is unparseable), GET
                            the email by id and use `from_email`.
                        recipient:
                          type: string
                        subject:
                          type:
                            - string
                            - "null"
                        domain:
                          type: string
                        spam_score:
                          type:
                            - number
                            - "null"
                        created_at:
                          type: string
                          format: date-time
                        received_at:
                          type: string
                          format: date-time
                        raw_size_bytes:
                          type:
                            - integer
                            - "null"
                        webhook_status:
                          type:
                            - string
                            - "null"
                          description: |
                            Webhook-delivery state for an inbound email. Tracks a
                            SEPARATE lifecycle from the email's `status` field; the
                            same row carries both. Possible values:

                              - `pending`: ingestion is past `pending` (the email itself
                                is `accepted`) but the webhook fan-out has not yet
                                started for this row.
                              - `in_flight`: at least one delivery attempt is in flight.
                              - `fired`: terminal success. Every active endpoint
                                acknowledged the delivery (or accepted it after retries).
                              - `failed`: terminal partial-failure. At least one endpoint
                                exhausted its retry budget; some endpoints may still
                                have succeeded.
                              - `exhausted`: terminal failure. Every endpoint exhausted
                                its retry budget without success.
                              - `null`: no endpoints configured, so no webhook lifecycle
                                applies.

                            Note that the value `pending` here does NOT mean the email
                            is `pending`; it means the email is past ingestion but
                            webhook delivery has not yet begun. Two overlapping uses
                            of the word `pending` for distinct lifecycle phases.
                          enum:
                            - pending
                            - in_flight
                            - fired
                            - failed
                            - exhausted
                            - null
                        webhook_attempt_count:
                          type: integer
                        thread_id:
                          type:
                            - string
                            - "null"
                          format: uuid
                          description: |
                            Conversation thread this message belongs to. Fetch
                            `/threads/{thread_id}` for the full ordered thread. NULL on
                            messages received before threading was enabled.
                      required:
                        - id
                        - status
                        - sender
                        - recipient
                        - domain
                        - created_at
                        - received_at
                        - webhook_attempt_count
                required:
                  - success
                  - demo
                  - data
        "429":
          $ref: "#/components/responses/RateLimited"
          description: Demo rate limit reached.
  /demo/emails/{id}:
    get:
      operationId: getDemoEmail
      summary: Get one sample email without authentication
      description: Public, no-auth demo. Returns a single synthetic email shaped exactly like the authenticated `GET /emails/{id}` response (`EmailDetail` schema). No real tenant data is exposed. Rate-limited per IP.
      tags:
        - Demo
      security: []
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
          description: Any value; the demo returns a fixed sample.
      responses:
        "200":
          description: Synthetic email detail, same shape as the authenticated /emails/{id} response.
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    const: true
                  demo:
                    type: boolean
                    const: true
                  note:
                    type: string
                    description: Reminder that the data is synthetic.
                  data:
                    type: object
                    properties:
                      id:
                        type: string
                        format: uuid
                      message_id:
                        type:
                          - string
                          - "null"
                      domain_id:
                        type:
                          - string
                          - "null"
                        format: uuid
                      org_id:
                        type:
                          - string
                          - "null"
                        format: uuid
                      sender:
                        type: string
                        description: |
                          SMTP envelope sender (return-path) the inbound mail server
                          accepted. Same value as `smtp_mail_from`; both fields exist
                          so protocol-aware tooling can use whichever name it expects.

                          For most legitimate mail this equals `from_email`; for
                          mailing lists, bounce handlers, and forwarders it is
                          typically the bounce-handling address rather than the
                          human-visible sender.

                          **For the canonical "who sent this email" value, use
                          `from_email`.**
                      recipient:
                        type: string
                      subject:
                        type:
                          - string
                          - "null"
                      body_text:
                        type:
                          - string
                          - "null"
                        description: Plain-text body parsed from the inbound MIME, matching the `email.parsed.body_text` field on the webhook payload. Null when the message had no text part or parsing failed.
                      body_html:
                        type:
                          - string
                          - "null"
                        description: HTML body parsed from the inbound MIME, matching the `email.parsed.body_html` field on the webhook payload. Null when the message had no HTML part or parsing failed.
                      status:
                        type: string
                        description: |
                          Lifecycle status of an INBOUND email (a row in the `emails`
                          table). Distinct from `SentEmailStatus`, which describes
                          the OUTBOUND lifecycle (the `sent_emails` table) and uses
                          a different vocabulary because the lifecycles differ.
                          Possible values:

                            - `pending`: the row was inserted at ingestion (mx_main)
                              and has not yet completed the spam / filter / auth
                              pipeline. Body and parsed fields are present; webhook
                              delivery is not yet scheduled. Most rows transition out
                              of `pending` within seconds.
                            - `accepted`: the inbound passed the policy gates and is
                              queued for webhook delivery. The `webhook_status` field
                              tracks the separate webhook-delivery lifecycle from
                              this point.
                            - `completed`: terminal success. Webhook delivery
                              attempted and acknowledged by every active endpoint, OR
                              no endpoints are configured, so the row is durably
                              archived.
                            - `rejected`: terminal failure at ingestion (spam, blocked
                              sender, filter rule, malformed). The body and metadata
                              are stored for auditing but no webhook fires and the
                              row is not repliable.

                          See also `webhook_status` (separate enum tracking the
                          webhook-delivery state machine) and `SentEmailStatus` (the
                          outbound vocabulary).
                        enum:
                          - pending
                          - accepted
                          - completed
                          - rejected
                      domain:
                        type: string
                      spam_score:
                        type:
                          - number
                          - "null"
                      raw_size_bytes:
                        type:
                          - integer
                          - "null"
                      raw_sha256:
                        type:
                          - string
                          - "null"
                      created_at:
                        type: string
                        format: date-time
                      received_at:
                        type: string
                        format: date-time
                      rejection_reason:
                        type:
                          - string
                          - "null"
                      webhook_status:
                        type:
                          - string
                          - "null"
                        description: |
                          Webhook-delivery state for an inbound email. Tracks a
                          SEPARATE lifecycle from the email's `status` field; the
                          same row carries both. Possible values:

                            - `pending`: ingestion is past `pending` (the email itself
                              is `accepted`) but the webhook fan-out has not yet
                              started for this row.
                            - `in_flight`: at least one delivery attempt is in flight.
                            - `fired`: terminal success. Every active endpoint
                              acknowledged the delivery (or accepted it after retries).
                            - `failed`: terminal partial-failure. At least one endpoint
                              exhausted its retry budget; some endpoints may still
                              have succeeded.
                            - `exhausted`: terminal failure. Every endpoint exhausted
                              its retry budget without success.
                            - `null`: no endpoints configured, so no webhook lifecycle
                              applies.

                          Note that the value `pending` here does NOT mean the email
                          is `pending`; it means the email is past ingestion but
                          webhook delivery has not yet begun. Two overlapping uses
                          of the word `pending` for distinct lifecycle phases.
                        enum:
                          - pending
                          - in_flight
                          - fired
                          - failed
                          - exhausted
                          - null
                      webhook_attempt_count:
                        type: integer
                      webhook_last_attempt_at:
                        type:
                          - string
                          - "null"
                        format: date-time
                      webhook_last_status_code:
                        type:
                          - integer
                          - "null"
                      webhook_last_error:
                        type:
                          - string
                          - "null"
                      webhook_fired_at:
                        type:
                          - string
                          - "null"
                        format: date-time
                      smtp_helo:
                        type:
                          - string
                          - "null"
                      smtp_mail_from:
                        type:
                          - string
                          - "null"
                        description: |
                          SMTP envelope MAIL FROM (return-path), as accepted by the
                          inbound mail server. Same value as `sender`; both fields
                          exist so protocol-aware tooling can use whichever name it
                          expects.

                          For the canonical "who sent this email" value (display name
                          stripped, From-header preferred), use `from_email`.
                      smtp_rcpt_to:
                        type:
                          - array
                          - "null"
                        items:
                          type: string
                      from_header:
                        type:
                          - string
                          - "null"
                        description: |
                          Raw `From:` header from the message body, including any
                          display name (e.g. `"Alice Example" <alice@example.com>`).
                          Use this when you need the display name for rendering.

                          For the bare email address (display name stripped), use
                          `from_email`.
                      content_discarded_at:
                        type:
                          - string
                          - "null"
                        format: date-time
                      content_discarded_by_delivery_id:
                        type:
                          - string
                          - "null"
                      from_email:
                        type: string
                        description: |
                          Bare email address parsed from the `From:` header, with
                          display name stripped (e.g. `alice@example.com`). Falls
                          back to `sender` (the SMTP envelope MAIL FROM) when the
                          `From:` header cannot be parsed.

                          **This is the canonical "who sent this email" field for
                          most use cases**, including comparing against allowlists,
                          routing replies, or displaying the sender to a user. Use
                          `from_header` when you specifically need the display name,
                          or `sender`/`smtp_mail_from` when you need the SMTP
                          envelope value (e.g. to follow a bounce).
                      to_email:
                        type: string
                        description: Parsed to address (same as recipient)
                      from_known_address:
                        type: boolean
                        description: |
                          True when the inbound's sender address has a matching grant
                          in the org's known-send-addresses list. Advisory: a true
                          value does not by itself guarantee that a reply will be
                          accepted by send-mail's gates; the per-send check at send
                          time remains authoritative.
                      replies:
                        type: array
                        description: |
                          Sent emails recorded as replies to this inbound, in send
                          order (ascending). Populated when a customer's send-mail
                          request carries an `in_reply_to` Message-ID that matches
                          this inbound's `message_id` in the same org. Includes
                          attempts that were gate-denied, so the array reflects every
                          recorded reply attempt regardless of outcome.
                        items:
                          type: object
                          properties:
                            id:
                              type: string
                              format: uuid
                              description: Sent-email row id.
                            status:
                              type: string
                              description: |
                                Lifecycle status of a sent_emails row. Possible values:

                                  - `queued`: pre-call INSERT; the outbound agent has not
                                    yet replied.
                                  - `submitted_to_agent`: agent accepted; `queue_id` is set.
                                  - `agent_failed`: agent rejected; `error_code` and
                                    `error_message` carry the reason.
                                  - `gate_denied`: a recipient-scope gate denied the send;
                                    the agent was never called. The `gates` array carries
                                    the denial detail. /send-mail returns 403 in this case
                                    so callers see the denial synchronously; /sent-emails
                                    additionally records the row for historical lookup,
                                    which is when this status appears in a listing.
                                  - `unknown`: terminal indeterminate; the on-box log
                                    poller couldn't classify the receiver's response.
                                  - `delivered` / `bounced` / `deferred` / `wait_timeout`:
                                    terminal delivery outcomes (see DeliveryStatus).
                              enum:
                                - queued
                                - submitted_to_agent
                                - agent_failed
                                - gate_denied
                                - unknown
                                - delivered
                                - bounced
                                - deferred
                                - wait_timeout
                            to_address:
                              type: string
                              description: Recipient address as recorded on the sent_emails row.
                            subject:
                              type:
                                - string
                                - "null"
                            created_at:
                              type: string
                              format: date-time
                            queue_id:
                              type:
                                - string
                                - "null"
                              description: Outbound relay queue identifier when available.
                          required:
                            - id
                            - status
                            - to_address
                            - created_at
                      reply_to_sent_email_id:
                        type:
                          - string
                          - "null"
                        format: uuid
                        description: |
                          The `sent_emails.id` of the outbound this inbound was a
                          reply to, when resolvable. Set at inbound ingest by
                          matching the parsed In-Reply-To (or References, as a
                          fallback) against `sent_emails.message_id` in the same
                          org. The mirror of `sent_emails.in_reply_to_email_id` for
                          the inbound side of a thread. NULL when the inbound is
                          not a threaded reply to one of your sends, when neither
                          header survived the path through intermediate MTAs, or on
                          inbound received before this auto-link landed.
                      thread_id:
                        type:
                          - string
                          - "null"
                        format: uuid
                        description: |
                          Conversation thread this message belongs to. Inbound and
                          outbound messages in the same conversation share a
                          `thread_id`; fetch `/threads/{thread_id}` for the full
                          ordered thread. Assigned at ingest. NULL on messages
                          received before threading was enabled (until backfilled).
                      parsed:
                        allOf:
                          - type: object
                            description: |
                              Parsed MIME content for an inbound email. Mirrors the
                              `email.parsed` object on the webhook payload so a single parser
                              handles both surfaces. `status` is `complete` when parsing
                              succeeded; on `failed` the body/address/attachment fields are
                              absent and `error` describes why.
                            properties:
                              status:
                                type: string
                                enum:
                                  - complete
                                  - failed
                              body_text:
                                type:
                                  - string
                                  - "null"
                                description: Plain-text body. Present when `status` is `complete`.
                              body_html:
                                type:
                                  - string
                                  - "null"
                                description: HTML body. Present when `status` is `complete`.
                              reply_to:
                                type:
                                  - array
                                  - "null"
                                items:
                                  type: object
                                  description: A parsed RFC 5322 address with optional display name.
                                  properties:
                                    name:
                                      type:
                                        - string
                                        - "null"
                                      description: Display name, when present (e.g. `Alice Example`).
                                    address:
                                      type: string
                                      description: Bare email address (e.g. `alice@example.com`).
                                  required:
                                    - address
                                description: Parsed `Reply-To` header addresses.
                              cc:
                                type:
                                  - array
                                  - "null"
                                items:
                                  type: object
                                  description: A parsed RFC 5322 address with optional display name.
                                  properties:
                                    name:
                                      type:
                                        - string
                                        - "null"
                                      description: Display name, when present (e.g. `Alice Example`).
                                    address:
                                      type: string
                                      description: Bare email address (e.g. `alice@example.com`).
                                  required:
                                    - address
                                description: Parsed `Cc` header addresses.
                              bcc:
                                type:
                                  - array
                                  - "null"
                                items:
                                  type: object
                                  description: A parsed RFC 5322 address with optional display name.
                                  properties:
                                    name:
                                      type:
                                        - string
                                        - "null"
                                      description: Display name, when present (e.g. `Alice Example`).
                                    address:
                                      type: string
                                      description: Bare email address (e.g. `alice@example.com`).
                                  required:
                                    - address
                                description: Parsed `Bcc` header addresses (rarely present on inbound).
                              to_addresses:
                                type:
                                  - array
                                  - "null"
                                items:
                                  type: object
                                  description: A parsed RFC 5322 address with optional display name.
                                  properties:
                                    name:
                                      type:
                                        - string
                                        - "null"
                                      description: Display name, when present (e.g. `Alice Example`).
                                    address:
                                      type: string
                                      description: Bare email address (e.g. `alice@example.com`).
                                  required:
                                    - address
                                description: Parsed `To` header addresses.
                              in_reply_to:
                                type:
                                  - array
                                  - "null"
                                items:
                                  type: string
                                description: Message-IDs from the `In-Reply-To` header.
                              references:
                                type:
                                  - array
                                  - "null"
                                items:
                                  type: string
                                description: Message-IDs from the `References` header.
                              attachments:
                                type: array
                                items:
                                  type: object
                                  description: |
                                    Metadata for one attachment. The bytes are not inline; download
                                    all attachments for a message as a gzipped tarball via
                                    `/emails/{id}/attachments.tar.gz`. `sha256` lets you verify a
                                    specific part after extraction.
                                  properties:
                                    filename:
                                      type:
                                        - string
                                        - "null"
                                    content_type:
                                      type:
                                        - string
                                        - "null"
                                    size_bytes:
                                      type: integer
                                    sha256:
                                      type:
                                        - string
                                        - "null"
                                    part_index:
                                      type: integer
                                      description: Zero-based index of this part within the message.
                                  required:
                                    - size_bytes
                                description: Attachment metadata. Empty array when none.
                              error:
                                type:
                                  - object
                                  - "null"
                                description: |
                                  Present (non-null) only when `status` is `failed`. When
                                  present, all three fields are populated, so a consumer can
                                  branch on `code` without defensive null checks.
                                properties:
                                  code:
                                    type: string
                                    description: Stable failure code (e.g. `PARSE_FAILED`).
                                  message:
                                    type: string
                                  retryable:
                                    type: boolean
                                required:
                                  - code
                                  - message
                                  - retryable
                            required:
                              - status
                        description: |
                          Parsed MIME content (addresses, threading headers,
                          attachment metadata), matching the `email.parsed` object
                          on the webhook payload so one parser handles both the
                          webhook and this endpoint. The top-level `body_text` /
                          `body_html` fields above are the same values as
                          `parsed.body_text` / `parsed.body_html`, retained for
                          backward compatibility.
                      auth:
                        allOf:
                          - type: object
                            description: |
                              SPF / DKIM / DMARC verdicts computed at ingest. Mirrors the
                              `email.auth` object on the webhook payload. Field names are
                              camelCase to match that payload exactly. For messages received
                              before auth was recorded, the verdicts default to `none`.
                            properties:
                              spf:
                                type: string
                                description: SPF result (e.g. `pass`, `fail`, `softfail`, `none`).
                              dmarc:
                                type: string
                                description: DMARC result (e.g. `pass`, `fail`, `none`).
                              dmarcPolicy:
                                type:
                                  - string
                                  - "null"
                                description: Published DMARC policy (`none`, `quarantine`, `reject`).
                              dmarcFromDomain:
                                type:
                                  - string
                                  - "null"
                                description: The From-header domain DMARC was evaluated against.
                              dmarcSpfAligned:
                                type: boolean
                              dmarcDkimAligned:
                                type: boolean
                              dmarcSpfStrict:
                                type:
                                  - boolean
                                  - "null"
                              dmarcDkimStrict:
                                type:
                                  - boolean
                                  - "null"
                              dkimSignatures:
                                type: array
                                items:
                                  type: object
                                  description: One DKIM signature found on the message, with its verdict.
                                  properties:
                                    domain:
                                      type: string
                                    selector:
                                      type: string
                                    result:
                                      type: string
                                      description: Verification result (e.g. `pass`, `fail`, `none`).
                                    aligned:
                                      type: boolean
                                      description: Whether the signing domain aligns with the From domain (for DMARC).
                                    keyBits:
                                      type:
                                        - integer
                                        - "null"
                                    algo:
                                      type:
                                        - string
                                        - "null"
                                  required:
                                    - domain
                                    - selector
                                    - result
                                    - aligned
                            required:
                              - spf
                              - dmarc
                              - dmarcSpfAligned
                              - dmarcDkimAligned
                              - dkimSignatures
                        description: |
                          SPF / DKIM / DMARC verdicts computed at ingest, matching
                          the `email.auth` object on the webhook payload. Use these
                          to decide how much to trust a message before acting on
                          instructions it contains.
                    required:
                      - id
                      - sender
                      - recipient
                      - status
                      - domain
                      - created_at
                      - received_at
                      - webhook_attempt_count
                      - from_email
                      - to_email
                      - replies
                      - parsed
                      - auth
                required:
                  - success
                  - demo
                  - data
        "429":
          $ref: "#/components/responses/RateLimited"
          description: Demo rate limit reached.
  /discovery:
    get:
      operationId: getDiscovery
      summary: List the unauthenticated API endpoints
      description: "Public, no-auth entry point for arriving agents. Returns the API base URL, the authentication lifecycle (register/claim/revoke), and the list of operations callable without credentials — the agent/CLI signup + login flows and the no-account `POST /send-mail/demo`. Fetch this first to discover how to obtain credentials, then call the authenticated operations with `Authorization: Bearer prim_<key>`."
      tags:
        - Discovery
      security: []
      responses:
        "200":
          description: Discovery document listing the unauthenticated endpoints and how to obtain credentials.
          content:
            application/json:
              schema:
                type: object
                properties:
                  service:
                    type: string
                  description:
                    type: string
                  base_url:
                    type: string
                    format: uri
                  documentation:
                    type: string
                    format: uri
                  openapi:
                    type: string
                    format: uri
                  authentication:
                    type: object
                    properties:
                      guide:
                        type: string
                        format: uri
                      register_uri:
                        type: string
                        format: uri
                      claim_uri:
                        type: string
                        format: uri
                      revocation_uri:
                        type: string
                        format: uri
                    required:
                      - guide
                      - register_uri
                      - claim_uri
                      - revocation_uri
                  public_endpoints:
                    type: array
                    items:
                      type: object
                      properties:
                        method:
                          type: string
                        path:
                          type: string
                        url:
                          type: string
                          format: uri
                        summary:
                          type: string
                        authentication:
                          type: string
                          const: none
                      required:
                        - method
                        - path
                        - url
                        - summary
                        - authentication
                required:
                  - service
                  - base_url
                  - authentication
                  - public_endpoints
              example:
                service: Primitive — email infrastructure for AI agents
                description: "These endpoints are callable without authentication. Use the signup flows to register an agent identity and obtain credentials, or the demo to exercise the API before you have one. Every other endpoint requires `Authorization: Bearer <token>`."
                base_url: https://api.primitive.dev/v1
                documentation: https://docs.primitive.dev/docs
                openapi: https://www.primitive.dev/openapi.json
                authentication:
                  guide: https://www.primitive.dev/auth.md
                  register_uri: https://api.primitive.dev/v1/agent/signup/start
                  claim_uri: https://api.primitive.dev/v1/agent/signup/verify
                  revocation_uri: https://www.primitive.dev/oauth/revoke
                public_endpoints:
                  - method: POST
                    path: /agent/signup/start
                    url: https://api.primitive.dev/v1/agent/signup/start
                    summary: Start agent account signup
                    authentication: none
                  - method: POST
                    path: /send-mail/demo
                    url: https://api.primitive.dev/v1/send-mail/demo
                    summary: Try send-mail without authentication (simulation — no mail is sent)
                    authentication: none
  /ask:
    servers:
      - url: https://www.primitive.dev
        description: Web origin — NLWeb endpoint host (not the v1 API base).
    get:
      operationId: askGet
      summary: Ask about Primitive — NLWeb query (no authentication)
      description: "Microsoft NLWeb natural-language query endpoint. Returns structured JSON describing Primitive. **No credentials required.** Pass the question as `?q=...`; send `Accept: text/event-stream` for an SSE stream."
      tags:
        - Discovery
      security: []
      parameters:
        - name: q
          in: query
          required: false
          schema:
            type: string
            maxLength: 500
          description: Natural-language question about Primitive.
      responses:
        "200":
          description: NLWeb result list describing Primitive capabilities.
          content:
            application/json:
              schema:
                type: object
                properties:
                  _meta:
                    type: object
                    properties:
                      response_type:
                        type: string
                      version:
                        type: string
                      query:
                        type: string
                  results:
                    type: array
                    items:
                      type: object
                      properties:
                        "@context":
                          type: string
                        "@type":
                          type: string
                        name:
                          type: string
                        url:
                          type: string
                        description:
                          type: string
                required:
                  - _meta
                  - results
    post:
      operationId: ask
      summary: Ask about Primitive — NLWeb query (no authentication)
      description: 'Microsoft NLWeb natural-language query endpoint. POST `{ "q": "..." }`. **No credentials required.** Set `prefer.streaming: true` or `Accept: text/event-stream` for an SSE stream.'
      tags:
        - Discovery
      security: []
      requestBody:
        required: false
        content:
          application/json:
            schema:
              type: object
              properties:
                q:
                  type: string
                  maxLength: 500
                  description: Natural-language question about Primitive.
                prefer:
                  type: object
                  properties:
                    streaming:
                      type: boolean
            example:
              q: What does Primitive cost?
      responses:
        "200":
          description: NLWeb result list describing Primitive capabilities.
          content:
            application/json:
              schema:
                type: object
                properties:
                  _meta:
                    type: object
                    properties:
                      response_type:
                        type: string
                      version:
                        type: string
                      query:
                        type: string
                  results:
                    type: array
                    items:
                      type: object
                      properties:
                        "@context":
                          type: string
                        "@type":
                          type: string
                        name:
                          type: string
                        url:
                          type: string
                        description:
                          type: string
                required:
                  - _meta
                  - results
      parameters:
        - name: Idempotency-Key
          in: header
          required: false
          description: Optional client-supplied idempotency key. Retrying a request with the same key returns the original result instead of performing the action a second time; if omitted the server derives one from the canonical payload hash. Safe to retry network failures without duplicating side effects.
          schema:
            type: string
            minLength: 1
            maxLength: 255
  /health:
    get:
      operationId: getHealth
      summary: Service health check
      description: Liveness probe for the API. Returns 200 with a small status payload whenever the service is up. Public — no authentication required — so agents, uptime monitors, and load balancers can reach it without a credential.
      tags:
        - Service
      security: []
      responses:
        "200":
          description: The service is healthy.
          content:
            application/json:
              schema:
                type: object
                additionalProperties: false
                properties:
                  status:
                    type: string
                    enum:
                      - ok
                    description: Always `ok` when the service is healthy.
                  env:
                    type: string
                    description: Deployment environment identifier.
                  build_sha:
                    type: string
                    description: Git commit SHA of the running build.
                  ts:
                    type: string
                    format: date-time
                    description: Server timestamp (ISO 8601).
                  capabilities:
                    type: object
                    additionalProperties: false
                    description: Operational rollout capabilities of the running API build.
                    properties:
                      webhook_secret_rotation:
                        type: string
                        enum:
                          - armed
                          - disabled
                        description: Whether this core-api deployment is locally armed for webhook-secret rotation. Operators must independently confirm all Function-delivery producers have shipped dual signing before arming it.
                    required:
                      - webhook_secret_rotation
                required:
                  - status
                  - env
                  - build_sha
                  - ts
                  - capabilities
              example:
                status: ok
                env: production
                build_sha: 0c0f184
                ts: 2026-06-10T10:00:00.000Z
                capabilities:
                  webhook_secret_rotation: armed
components:
  securitySchemes:
    BearerAuth:
      type: http
      scheme: bearer
      description: "API key with `prim_` prefix or OAuth access token with `prim_oat_` prefix: `Authorization: Bearer <token>`. Access is governed by the caller's organization role (`owner`, `admin`, or `member`): API keys always act at `member` level regardless of who created them, and OAuth access tokens act with the authorizing user's current organization role, resolved per request. Every operation in this spec is available to organization members; billing and organization administration are owner/admin actions performed in the dashboard and are not part of this API."
    DownloadToken:
      type: apiKey
      in: query
      name: token
      description: Signed download token provided in webhook payloads
  parameters:
    ResourceId:
      name: id
      in: path
      required: true
      schema:
        type: string
        format: uuid
      description: Resource UUID
    IdempotencyKey:
      name: Idempotency-Key
      in: header
      required: false
      description: Optional client-supplied idempotency key. Retrying a request with the same key returns the original result instead of performing the action a second time; if omitted the server derives one from the canonical payload hash. Safe to retry network failures without duplicating side effects.
      schema:
        type: string
        minLength: 1
        maxLength: 255
    Cursor:
      name: cursor
      in: query
      schema:
        type: string
      description: |
        Pagination cursor from a previous response's `meta.cursor` field.
        Format: `{ISO-datetime}|{id}`
    Limit:
      name: limit
      in: query
      schema:
        type: integer
        minimum: 1
        maximum: 100
        default: 50
      description: Number of results per page
    MemoryKeyQuery:
      name: key
      in: query
      required: true
      description: Memory key. Must be at most 512 UTF-8 bytes.
      schema:
        type: string
        minLength: 1
        maxLength: 512
    MemoryScopeQueryType:
      name: scope_type
      in: query
      required: false
      description: |
        Explicit scope type. Omit to use automatic scope resolution. Pass
        `function` with `scope_id=<function-id>`, or `org` with no `scope_id`.
      schema:
        type: string
        enum:
          - org
          - function
    MemoryScopeId:
      name: scope_id
      in: query
      required: false
      description: |
        Function id UUID when `scope_type=function`. Not valid with
        `scope_type=org`.
      schema:
        type: string
        format: uuid
  responses:
    Unauthorized:
      description: Invalid or missing API key
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/ErrorResponse"
          example:
            success: false
            error:
              code: unauthorized
              message: Invalid or missing API key
    Forbidden:
      description: Authenticated caller lacks permission for the operation
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/ErrorResponse"
          example:
            success: false
            error:
              code: forbidden
              message: Insufficient permissions
    NotFound:
      description: Resource not found
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/ErrorResponse"
          example:
            success: false
            error:
              code: not_found
              message: Resource not found
    ValidationError:
      description: Invalid request parameters
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/ErrorResponse"
          example:
            success: false
            error:
              code: validation_error
              message: Invalid domain format
    PaymentRequired:
      description: Usage credits are exhausted or payment is required.
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/ErrorResponse"
    DeployFailed:
      description: Function deploy could not be completed; previously deployed code remains live
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/ErrorResponse"
          example:
            success: false
            error:
              code: deploy_failed
              message: Function deploy failed
    RateLimited:
      description: Rate limit exceeded
      headers:
        Retry-After:
          schema:
            type: integer
          description: Seconds to wait before retrying
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/ErrorResponse"
          example:
            success: false
            error:
              code: rate_limit_exceeded
              message: Rate limit exceeded
    BadGateway:
      description: Primitive could not complete the downstream SMTP request
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/ErrorResponse"
          example:
            success: false
            error:
              code: outbound_unreachable
              message: Outbound SMTP service request failed
    ServiceUnavailable:
      description: Primitive is temporarily unable to process the request
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/ErrorResponse"
          example:
            success: false
            error:
              code: outbound_capacity_exhausted
              message: Outbound capacity is temporarily exhausted
    InternalError:
      description: Primitive encountered an internal error
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/ErrorResponse"
          example:
            success: false
            error:
              code: internal_error
              message: Internal server error
    Conflict:
      description: The request conflicts with the current state of the resource
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/ErrorResponse"
          example:
            success: false
            error:
              code: conflict
              message: settlement already in progress
    UnprocessableEntity:
      description: |
        The request was well-formed but could not be processed. For Payments
        this covers a missing payout address, a failed payment verification, a
        spend-policy decline, or an expired challenge; `error.code` distinguishes
        them.
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/ErrorResponse"
          example:
            success: false
            error:
              code: payment_declined
              message: payment exceeds the per-payment cap
    Deleted:
      description: Resource deleted
      content:
        application/json:
          schema:
            allOf:
              - $ref: "#/components/schemas/SuccessEnvelope"
              - type: object
                properties:
                  data:
                    type: object
                    properties:
                      deleted:
                        type: boolean
                        const: true
                    required:
                      - deleted
    Gone:
      description: Resource is no longer available.
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/ErrorResponse"
          example:
            success: false
            error:
              code: not_found
              message: Resource is no longer available
    PayloadTooLarge:
      description: Request body is too large.
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/ErrorResponse"
          example:
            success: false
            error:
              code: validation_error
              message: Request body is too large
    UnsupportedMediaType:
      description: Content-Type must be application/json.
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/ErrorResponse"
          example:
            success: false
            error:
              code: validation_error
              message: Content-Type must be application/json
    FailedDependency:
      description: The operation depends on another service or step that failed.
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/ErrorResponse"
          example:
            success: false
            error:
              code: deploy_failed
              message: Function deploy failed
    GatewayTimeout:
      description: Primitive timed out while waiting for a downstream operation.
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/ErrorResponse"
          example:
            success: false
            error:
              code: internal_error
              message: Downstream operation timed out
  schemas:
    PublishPolicy:
      type: string
      enum:
        - owner_only
        - request
        - open
      description: |
        Who may publish into a registry. owner_only: only the registry owner.
        request: anyone may request and the owner approves. open: anyone may
        publish and it lists immediately (no approval step).
    Registry:
      type: object
      properties:
        id:
          type: string
          format: uuid
        slug:
          type: string
        name:
          type: string
        description:
          type:
            - string
            - "null"
        is_public:
          type: boolean
        publish_policy:
          $ref: "#/components/schemas/PublishPolicy"
      required:
        - id
        - slug
        - name
        - description
        - is_public
        - publish_policy
    RegistryAgent:
      type: object
      description: An agent's public directory profile.
      properties:
        address:
          type: string
        display_name:
          type: string
        title:
          type:
            - string
            - "null"
        description:
          type:
            - string
            - "null"
        tags:
          type: array
          items:
            type: string
        handle:
          type:
            - string
            - "null"
          description: The registry-scoped name. Null on the global by-address read.
        last_reachable_at:
          type:
            - string
            - "null"
          format: date-time
          description: When the agent's address was last confirmed to still route to its endpoint. A freshness signal for ranking listings; null until the first check.
      required:
        - address
        - display_name
        - title
        - description
        - tags
        - handle
        - last_reachable_at
    RegistryRequest:
      type: object
      description: A pending publication request, as the registry owner sees it.
      properties:
        id:
          type: string
          format: uuid
        address:
          type: string
        display_name:
          type: string
        handle:
          type:
            - string
            - "null"
        requested_at:
          type: string
          format: date-time
      required:
        - id
        - address
        - display_name
        - handle
        - requested_at
    CreateRegistryInput:
      type: object
      properties:
        slug:
          type: string
          pattern: ^[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?$
          description: Lowercase slug, unique across registries.
        name:
          type: string
          minLength: 1
          maxLength: 120
        description:
          type:
            - string
            - "null"
          maxLength: 2000
        is_public:
          type: boolean
        publish_policy:
          $ref: "#/components/schemas/PublishPolicy"
      required:
        - slug
        - name
    UpdateRegistryInput:
      type: object
      minProperties: 1
      properties:
        name:
          type: string
          minLength: 1
          maxLength: 120
        description:
          type:
            - string
            - "null"
          maxLength: 2000
        is_public:
          type: boolean
        publish_policy:
          $ref: "#/components/schemas/PublishPolicy"
    DefineAgentInput:
      type: object
      properties:
        address:
          type: string
          description: The agent's globally unique email address; mail to it must route to an endpoint the account controls.
        endpoint_id:
          type: string
          format: uuid
          description: Optional. The endpoint the agent runs on. Omit it to resolve the endpoint from the address's routing automatically; supply it to pin a specific endpoint, which is then validated against the address's route.
        display_name:
          type: string
          minLength: 1
          maxLength: 120
        title:
          type:
            - string
            - "null"
          maxLength: 120
        description:
          type:
            - string
            - "null"
          maxLength: 2000
        tags:
          type: array
          items:
            type: string
            maxLength: 40
          maxItems: 20
      required:
        - address
        - display_name
    PublishAgentInput:
      type: object
      description: Publish an agent into a registry. When display_name is present the agent identity is defined (create-or-get by address) in the same call before publishing; omit the define fields to publish an already-defined agent.
      properties:
        address:
          type: string
        handle:
          type: string
          description: The registry-scoped name to list the agent under.
        display_name:
          type: string
          minLength: 1
          maxLength: 120
          description: Present to define the agent identity before publishing (define + publish in one call).
        endpoint_id:
          type: string
          format: uuid
          description: Optional, only used when defining. Omit to resolve the endpoint from the address's routing.
        title:
          type:
            - string
            - "null"
          maxLength: 120
        description:
          type:
            - string
            - "null"
          maxLength: 2000
        tags:
          type: array
          items:
            type: string
            maxLength: 40
          maxItems: 20
      required:
        - address
        - handle
      dependentRequired:
        endpoint_id:
          - display_name
        title:
          - display_name
        description:
          - display_name
        tags:
          - display_name
    PublishAgentResult:
      type: object
      properties:
        status:
          type: string
          enum:
            - approved
            - requested
          description: approved lists immediately; requested pends owner approval.
        handle:
          type: string
        idempotent_replay:
          type: boolean
          description: True when the publish matched an existing identical publication.
      required:
        - status
        - handle
        - idempotent_replay
    DecideRegistryRequestInput:
      type: object
      properties:
        decision:
          type: string
          enum:
            - approved
            - rejected
      required:
        - decision
    RegisterPayoutAddressInput:
      type: object
      additionalProperties: false
      properties:
        address:
          type: string
          pattern: ^0x[0-9a-fA-F]{40}$
          description: The payout address (your signer's own EVM address), 0x-prefixed.
        network:
          type: string
          enum:
            - base
            - base-sepolia
          description: The chain the address receives on.
        signature:
          type: string
          pattern: ^0x[0-9a-fA-F]+$
          description: |
            A `personal_sign` signature over the org-bound message produced by
            the SDK helper `buildPayoutRegistrationMessage`. Recovered and
            checked against `address`; the org id is bound into the signed bytes.
        issued_at:
          type: string
          format: date-time
          description: |
            ISO-8601 timestamp embedded in the signed message. Must be within a
            short freshness window (about 10 minutes) of server time.
        label:
          type: string
          maxLength: 80
          description: Optional human-readable label.
      required:
        - address
        - network
        - signature
        - issued_at
    X402PayoutAddress:
      type: object
      properties:
        id:
          type: string
          format: uuid
        address:
          type: string
          description: The checksummed payout address.
        network:
          type: string
          enum:
            - base
            - base-sepolia
        label:
          type:
            - string
            - "null"
        is_default:
          type: boolean
          description: Exactly one address per (org, network) is the default.
        verified_at:
          type: string
          format: date-time
          description: When ownership of the address was last proven.
        created_at:
          type: string
          format: date-time
      required:
        - id
        - address
        - network
        - label
        - is_default
        - verified_at
    CreateChallengeInput:
      type: object
      additionalProperties: false
      properties:
        amount:
          type: string
          pattern: ^[1-9][0-9]{0,38}$
          description: |
            Amount to collect, in token base units. USDC has 6 decimals, so
            `"10000"` is 0.01 USDC.
        network:
          type: string
          enum:
            - base
            - base-sepolia
        payer_org:
          type: string
          format: uuid
          description: |
            The org id allowed to pay this challenge (on-net binding). Optional.
        expires_in:
          type: integer
          minimum: 60
          maximum: 86400
          description: Seconds until the challenge expires. Defaults to 3600.
        resource:
          type: string
          format: uri
          maxLength: 2048
          description: |
            Optional URL identifying what is being paid for. Defaults to a
            synthetic `x402:challenge:<id>` identifier.
        description:
          type: string
          maxLength: 512
          description: Optional human-readable description of the payment.
      required:
        - amount
        - network
    X402NonceBinding:
      type: object
      description: |
        The interaction binding the payer hashes into the EIP-3009 nonce
        (`deriveEip3009Nonce`). Pinning the nonce to this binding is what lets an
        x402 payment ride asynchronous transports safely: a replayed challenge
        can't redirect funds and a signed payment can't settle twice.
      properties:
        interaction_id:
          type: string
          description: Interaction id, including its `@domain` part.
        challenge_step_id:
          type: string
          format: uuid
        challenge_nonce:
          type: string
          pattern: ^[0-9a-f]{64}$
          description: 32 random bytes as 64 lowercase hex chars.
      required:
        - interaction_id
        - challenge_step_id
        - challenge_nonce
    X402PaymentRequirements:
      type: object
      description: |
        The x402 `PaymentRequirements` the payer signs over. Field names are
        x402's native camelCase, preserved byte-for-byte.
      properties:
        scheme:
          type: string
          description: The x402 settlement scheme. Always `exact` for v1.
          example: exact
        network:
          type: string
          enum:
            - base
            - base-sepolia
        maxAmountRequired:
          type: string
          description: Amount in token base units.
        payTo:
          type: string
          description: The payee's resolved payout address (checksummed).
        asset:
          type: string
          description: The token contract address (checksummed). USDC.
        resource:
          type: string
        description:
          type: string
        maxTimeoutSeconds:
          type: integer
        extra:
          type: object
          description: |
            The token's load-bearing EIP-712 domain params. `name` differs by
            chain (Base mainnet USDC is `USD Coin`, Base Sepolia is `USDC`); a
            wrong value produces a signature the verifier rejects.
          properties:
            name:
              type: string
            version:
              type: string
          required:
            - name
            - version
      required:
        - scheme
        - network
        - maxAmountRequired
        - payTo
        - asset
        - extra
    X402Challenge:
      type: object
      properties:
        id:
          type: string
          format: uuid
        status:
          type: string
          enum:
            - pending
            - settling
            - settled
            - failed
            - expired
        network:
          type: string
          enum:
            - base
            - base-sepolia
        asset:
          type: string
          description: Token contract address (checksummed).
        amount:
          type: string
          description: Amount in token base units.
        pay_to:
          type: string
          description: The payee's resolved payout address (checksummed).
        payer_org:
          type:
            - string
            - "null"
          description: The org id bound as payer, if one was set at creation.
        resource:
          type:
            - string
            - "null"
        description:
          type:
            - string
            - "null"
        nonce_binding:
          $ref: "#/components/schemas/X402NonceBinding"
        settle_tx:
          type:
            - string
            - "null"
          description: On-chain settlement transaction hash once settled.
        settled_at:
          type:
            - string
            - "null"
          format: date-time
        failure_reason:
          type:
            - string
            - "null"
        expires_at:
          type: string
          format: date-time
        created_at:
          type: string
          format: date-time
        payment_requirements:
          description: |
            Present on the create response. Hand the whole challenge (including
            this) to the payer; `getChallenge` omits it (it is for status polling
            by the challenger).
          allOf:
            - $ref: "#/components/schemas/X402PaymentRequirements"
      required:
        - id
        - status
        - network
        - asset
        - amount
        - pay_to
        - nonce_binding
        - expires_at
    CreateEmailChallengeInput:
      type: object
      additionalProperties: false
      description: |
        Issue a payment challenge over an email thread. `from` is your sending
        address (the funds receiver; ownership is enforced at send, exactly as
        for outbound mail) and `to` is the payer's address. The `pay_to` payout
        wallet and the token asset are resolved server-side, never taken from
        the request.
      properties:
        from:
          type: string
          format: email
          description: |
            Your sending address (the payee / funds receiver). Must be an
            address your org is allowed to send from.
        to:
          type: string
          format: email
          description: The payer's email address the challenge is sent to.
        amount:
          type: string
          pattern: ^[1-9][0-9]{0,38}$
          description: |
            Amount to collect, in token base units (unlike the `charge` CLI
            command, which also accepts `--amount-usdc`, this field takes base
            units only). USDC has 6 decimals, so `"10000"` is 0.01 USDC:
            multiply a human USDC amount by 1,000,000 (0.01 USDC -> `"10000"`).
        network:
          type: string
          enum:
            - base
            - base-sepolia
        expires_in:
          type: integer
          minimum: 60
          maximum: 86400
          description: Seconds until the challenge expires. Defaults to 300.
        resource:
          type: string
          format: uri
          maxLength: 2048
          description: Optional URL identifying what is being paid for.
        description:
          type: string
          maxLength: 512
          description: Optional human-readable description of the payment.
      required:
        - from
        - to
        - amount
        - network
    X402EmailChallengeDetails:
      type: object
      description: |
        The challenge the payer needs to sign and pay, carried inside an
        email-native challenge response.
      properties:
        payment_requirements:
          $ref: "#/components/schemas/X402PaymentRequirements"
        nonce_binding:
          $ref: "#/components/schemas/X402NonceBinding"
        expires_at:
          type: string
          format: date-time
          description: ISO-8601 expiry of the challenge.
      required:
        - payment_requirements
        - nonce_binding
        - expires_at
    X402EmailChallenge:
      type: object
      description: |
        The result of issuing an email-native payment challenge. `interaction_id`
        is the real email thread id (`uuid@domain`) the payment is bound to;
        `challenge_id` is the underlying challenge record. Hand the `challenge`
        to the payer, who replies with a signed `payment` interaction step (the
        SDK `payEmailChallenge` helper builds it).
      properties:
        interaction_id:
          type: string
          description: The email thread id (`uuid@domain`) the payment is bound to.
        challenge_id:
          type: string
          format: uuid
          description: The underlying challenge record id.
        challenge:
          $ref: "#/components/schemas/X402EmailChallengeDetails"
      required:
        - interaction_id
        - challenge_id
        - challenge
    X402PaymentPayload:
      type: object
      description: |
        A signed x402 v1 `PaymentPayload`. The SDK `pay()` helper builds this;
        callers rarely construct it by hand. Field names are x402-native.
      properties:
        x402Version:
          type: integer
          const: 1
        scheme:
          type: string
          const: exact
        network:
          type: string
          enum:
            - base
            - base-sepolia
        payload:
          type: object
          properties:
            signature:
              type: string
              pattern: ^0x[0-9a-fA-F]+$
              description: The EIP-712 signature over the authorization.
            authorization:
              type: object
              description: The EIP-3009 `transferWithAuthorization` fields, as strings.
              properties:
                from:
                  type: string
                to:
                  type: string
                value:
                  type: string
                validAfter:
                  type: string
                validBefore:
                  type: string
                nonce:
                  type: string
              required:
                - from
                - to
                - value
                - validAfter
                - validBefore
                - nonce
          required:
            - signature
            - authorization
      required:
        - x402Version
        - scheme
        - network
        - payload
    PayChallengeInput:
      type: object
      additionalProperties: false
      properties:
        payment:
          $ref: "#/components/schemas/X402PaymentPayload"
      required:
        - payment
    X402Receipt:
      type: object
      properties:
        id:
          type: string
          format: uuid
        status:
          type: string
          enum:
            - settled
        settle_tx:
          type:
            - string
            - "null"
          description: On-chain settlement transaction hash.
      required:
        - id
        - status
        - settle_tx
    X402SpendPolicy:
      type: object
      description: |
        The payer's outbound spend policy. Returned with defaults (not paused,
        no caps, any on-net payee) when none is set.
      properties:
        paused:
          type: boolean
          description: Kill-switch. When true, all outbound payments are refused.
        max_per_payment:
          type:
            - string
            - "null"
          description: Per-payment cap in token base units, or null for no cap.
        max_per_day:
          type:
            - string
            - "null"
          description: Rolling-day cap in token base units, or null for no cap.
        allowlist:
          type:
            - array
            - "null"
          items:
            type: string
            format: uuid
          description: |
            Allowed payee org ids. `null` allows any on-net payee; `[]` denies
            all.
      required:
        - paused
        - max_per_payment
        - max_per_day
        - allowlist
    UpdateSpendPolicyInput:
      type: object
      additionalProperties: false
      description: |
        Merge update: only the fields you include change; omit a field to keep
        its current value; send `null` to clear a cap.
      properties:
        paused:
          type: boolean
        max_per_payment:
          type:
            - string
            - "null"
          pattern: ^[1-9][0-9]{0,38}$
        max_per_day:
          type:
            - string
            - "null"
          pattern: ^[1-9][0-9]{0,38}$
        allowlist:
          type:
            - array
            - "null"
          maxItems: 1000
          items:
            type: string
            format: uuid
    X402DeclinedPayment:
      type: object
      description: A payment the org's spend policy refused.
      properties:
        id:
          type: string
          format: uuid
        challenge_id:
          type:
            - string
            - "null"
          format: uuid
          description: The challenge that was declined, if still present.
        counterparty_org:
          type:
            - string
            - "null"
          format: uuid
          description: The payee (challenger) org, when known.
        network:
          type: string
          enum:
            - base
            - base-sepolia
        amount:
          type: string
          description: Amount in token base units.
        reason:
          type: string
          description: Why the payment was declined (cap, allowlist, paused).
        declined_at:
          type: string
          format: date-time
      required:
        - id
        - network
        - amount
        - reason
        - declined_at
    SuccessEnvelope:
      type: object
      properties:
        success:
          type: boolean
          const: true
      required:
        - success
        - data
    ListEnvelope:
      type: object
      properties:
        success:
          type: boolean
          const: true
        meta:
          $ref: "#/components/schemas/PaginationMeta"
      required:
        - success
        - data
        - meta
    PaginationMeta:
      type: object
      properties:
        total:
          type: integer
          description: Total number of matching records
        limit:
          type: integer
          description: Page size used for this request
        cursor:
          type:
            - string
            - "null"
          description: Cursor for the next page, or null if no more results
      required:
        - total
        - limit
        - cursor
    ErrorResponse:
      type: object
      properties:
        success:
          type: boolean
          const: false
        error:
          type: object
          properties:
            code:
              type: string
              enum:
                - unauthorized
                - forbidden
                - not_found
                - validation_error
                - rate_limit_exceeded
                - internal_error
                - conflict
                - mx_conflict
                - outbound_disabled
                - cannot_send_from_domain
                - recipient_not_allowed
                - outbound_key_missing
                - outbound_unreachable
                - outbound_key_invalid
                - outbound_capacity_exhausted
                - outbound_response_malformed
                - outbound_relay_failed
                - discard_not_enabled
                - inbound_not_repliable
                - search_timeout
                - authorization_pending
                - slow_down
                - access_denied
                - expired_token
                - invalid_device_code
                - invalid_signup_code
                - invalid_signup_token
                - invalid_verification_code
                - email_delivery_failed
                - clerk_signup_failed
                - no_orgs_for_user
                - org_not_accessible
                - feature_disabled
                - memory_conflict
                - developer_usage_credit_exhausted
                - no_payout_address
                - ownership_proof_failed
                - payment_verification_failed
                - payment_declined
                - challenge_expired
                - settlement_failed
            message:
              type: string
            details:
              type: object
              description: |
                Optional structured data that callers can inspect to recover
                from the error. The fields present depend on `code`. Additional
                keys may be added over time without a major-version bump.
              additionalProperties: true
              properties:
                mx_conflict:
                  type: object
                  description: Present when `code == mx_conflict`.
                  required:
                    - provider_name
                    - suggested_subdomain
                  properties:
                    provider_name:
                      type: string
                      description: Human-readable name of the detected mailbox provider (e.g. "Google Workspace").
                    suggested_subdomain:
                      type: string
                      description: Subdomain to try instead (e.g. "mail" for `mail.example.com`).
                required_entitlements:
                  type: array
                  items:
                    type: string
                  description: Entitlements that would allow a denied send when no recipient-scope gate was granted.
                sent_email_id:
                  type: string
                  description: ID of the persisted sent-email attempt associated with the error.
                content_hash:
                  type: string
                  description: Content hash of the original request on idempotency cache-hit errors.
                client_idempotency_key:
                  type: string
                  description: Effective idempotency key associated with the original request.
            gates:
              type: array
              items:
                $ref: "#/components/schemas/GateDenial"
              description: Structured per-gate denial detail for recipient-scope send-mail failures.
            request_id:
              type: string
              description: Server-issued request identifier for support and tracing.
          required:
            - code
            - message
      required:
        - success
        - error
    GateDenial:
      type: object
      properties:
        name:
          type: string
          enum:
            - send_to_confirmed_domains
            - send_to_known_addresses
          description: Public recipient-scope gate name that denied the send.
        reason:
          type: string
          enum:
            - domain_not_confirmed
            - recipient_unauthenticated
            - recipient_not_known
          description: Stable machine-readable denial reason.
        message:
          type: string
          description: Human-readable explanation of the gate denial.
        subject:
          type: string
          description: Domain or address the gate evaluated.
        fix:
          $ref: "#/components/schemas/GateFix"
        docs_url:
          type: string
          description: Public docs URL with more context.
      required:
        - name
        - reason
        - message
        - subject
    GateFix:
      type: object
      properties:
        action:
          type: string
          enum:
            - confirm_domain
            - sender_must_fix_authentication
            - wait_for_inbound
          description: Suggested next action for the caller.
        subject:
          type: string
          description: Entity the action applies to.
      required:
        - action
        - subject
    StartCliLoginInput:
      type: object
      additionalProperties: false
      properties:
        device_name:
          type: string
          minLength: 1
          maxLength: 80
          description: Human-readable device name shown during browser approval
        metadata:
          type: object
          additionalProperties: true
          description: Optional client metadata stored with the login session; serialized JSON must be 2048 bytes or fewer
    CliLoginStartResult:
      type: object
      properties:
        device_code:
          type: string
          description: Opaque code used by the CLI to poll for approval
        user_code:
          type: string
          pattern: ^[BCDFGHJKLMNPQRSTVWXZ]{4}-[BCDFGHJKLMNPQRSTVWXZ]{4}$
          description: Short code the user confirms in the browser
        verification_uri:
          type: string
          description: Browser URL where the user approves the login
        verification_uri_complete:
          type: string
          description: Browser URL with the user code prefilled
        expires_in:
          type: integer
          description: Seconds until the login session expires
        interval:
          type: integer
          description: Minimum seconds between poll requests
      required:
        - device_code
        - user_code
        - verification_uri
        - verification_uri_complete
        - expires_in
        - interval
    PollCliLoginInput:
      type: object
      additionalProperties: false
      properties:
        device_code:
          type: string
          minLength: 1
      required:
        - device_code
    CliLoginPollResult:
      type: object
      properties:
        api_key:
          type: string
          description: Legacy alias for access_token. New CLI builds should persist access_token and refresh_token.
        key_id:
          type: string
          format: uuid
          description: Legacy alias for oauth_grant_id
        key_prefix:
          type: string
          description: Legacy display prefix derived from access_token
        access_token:
          type: string
          description: OAuth access token for CLI API authentication
        refresh_token:
          type: string
          description: OAuth refresh token used by the CLI to renew access
        token_type:
          type: string
          enum:
            - Bearer
        expires_in:
          type: integer
          description: Seconds until access_token expires
        auth_method:
          type: string
          enum:
            - oauth
        oauth_grant_id:
          type: string
          format: uuid
        oauth_client_id:
          type: string
        org_id:
          type: string
          format: uuid
        org_name:
          type:
            - string
            - "null"
      required:
        - api_key
        - key_id
        - key_prefix
        - access_token
        - refresh_token
        - token_type
        - expires_in
        - auth_method
        - oauth_grant_id
        - oauth_client_id
        - org_id
        - org_name
    StartCliSignupInput:
      type: object
      additionalProperties: false
      properties:
        email:
          type: string
          format: email
          maxLength: 254
        signup_code:
          type: string
          minLength: 1
          maxLength: 128
          description: Optional signup code. Omit if you do not have one.
        terms_accepted:
          type: boolean
          const: true
          description: Must be true to confirm acceptance of Primitive's Terms of Service and Privacy Policy
        device_name:
          type: string
          minLength: 1
          maxLength: 80
          description: Human-readable device name used for the created CLI OAuth grant
        metadata:
          type: object
          additionalProperties: true
          description: Optional client metadata stored with the signup session; serialized JSON must be 2048 bytes or fewer
      required:
        - email
        - terms_accepted
    CliSignupStartResult:
      type: object
      properties:
        signup_token:
          type: string
          description: Opaque token used to verify or resend the pending CLI signup
        email:
          type: string
          format: email
        expires_in:
          type: integer
          description: Seconds until the pending signup expires
        resend_after:
          type: integer
          description: Minimum seconds before requesting another verification email
        verification_code_length:
          type: integer
          description: Number of digits in the emailed verification code
      required:
        - signup_token
        - email
        - expires_in
        - resend_after
        - verification_code_length
    ResendCliSignupVerificationInput:
      type: object
      additionalProperties: false
      properties:
        signup_token:
          type: string
          minLength: 1
      required:
        - signup_token
    CliSignupResendResult:
      type: object
      properties:
        email:
          type: string
          format: email
        expires_in:
          type: integer
          description: Seconds until the pending signup expires
        resend_after:
          type: integer
          description: Minimum seconds before requesting another verification email
        verification_code_length:
          type: integer
          description: Number of digits in the emailed verification code
      required:
        - email
        - expires_in
        - resend_after
        - verification_code_length
    VerifyCliSignupInput:
      type: object
      additionalProperties: false
      properties:
        signup_token:
          type: string
          minLength: 1
        verification_code:
          type: string
          minLength: 1
          maxLength: 32
        password:
          type: string
          minLength: 1
          maxLength: 1024
      required:
        - signup_token
        - verification_code
    CliSignupVerifyResult:
      type: object
      properties:
        api_key:
          type: string
          description: Legacy alias for access_token. New CLI builds should persist access_token and refresh_token.
        key_id:
          type: string
          format: uuid
          description: Legacy alias for oauth_grant_id
        key_prefix:
          type: string
          description: Legacy display prefix derived from access_token
        access_token:
          type: string
          description: OAuth access token for CLI API authentication
        refresh_token:
          type: string
          description: OAuth refresh token used by the CLI to renew access
        token_type:
          type: string
          enum:
            - Bearer
        expires_in:
          type: integer
          description: Seconds until access_token expires
        auth_method:
          type: string
          enum:
            - oauth
        oauth_grant_id:
          type: string
          format: uuid
        oauth_client_id:
          type: string
        org_id:
          type: string
          format: uuid
        org_name:
          type:
            - string
            - "null"
      required:
        - api_key
        - key_id
        - key_prefix
        - access_token
        - refresh_token
        - token_type
        - expires_in
        - auth_method
        - oauth_grant_id
        - oauth_client_id
        - org_id
        - org_name
    StartAgentSignupInput:
      type: object
      additionalProperties: false
      properties:
        email:
          type: string
          format: email
          maxLength: 254
        signup_code:
          type: string
          minLength: 1
          maxLength: 128
          description: Optional signup code. Omit if you do not have one.
        terms_accepted:
          type: boolean
          const: true
          description: Must be true to confirm acceptance of Primitive's Terms of Service and Privacy Policy
        device_name:
          type: string
          minLength: 1
          maxLength: 80
          description: Human-readable device name used for the created agent OAuth session
        metadata:
          type: object
          additionalProperties: true
          description: Optional client metadata stored with the signup session; serialized JSON must be 2048 bytes or fewer
      required:
        - email
        - terms_accepted
    AgentSignupStartResult:
      type: object
      properties:
        signup_token:
          type: string
          description: Opaque token used to verify or resend the pending agent signup
        email:
          type: string
          format: email
        expires_in:
          type: integer
          description: Seconds until the pending signup expires
        resend_after:
          type: integer
          description: Minimum seconds before requesting another verification email
        verification_code_length:
          type: integer
          description: Number of digits in the emailed verification code
      required:
        - signup_token
        - email
        - expires_in
        - resend_after
        - verification_code_length
    ResendAgentSignupVerificationInput:
      type: object
      additionalProperties: false
      properties:
        signup_token:
          type: string
          minLength: 1
      required:
        - signup_token
    AgentSignupResendResult:
      type: object
      properties:
        email:
          type: string
          format: email
        expires_in:
          type: integer
          description: Seconds until the pending signup expires
        resend_after:
          type: integer
          description: Minimum seconds before requesting another verification email
        verification_code_length:
          type: integer
          description: Number of digits in the emailed verification code
      required:
        - email
        - expires_in
        - resend_after
        - verification_code_length
    VerifyAgentSignupInput:
      type: object
      additionalProperties: false
      properties:
        signup_token:
          type: string
          minLength: 1
        verification_code:
          type: string
          minLength: 1
          maxLength: 32
        org_id:
          type: string
          format: uuid
          description: Optional workspace id to target when the verified email already belongs to multiple workspaces
      required:
        - signup_token
        - verification_code
    AgentOrgRef:
      type: object
      properties:
        id:
          type: string
          format: uuid
        name:
          type:
            - string
            - "null"
      required:
        - id
        - name
    AgentSignupVerifyResult:
      type: object
      properties:
        api_key:
          type: string
          description: Legacy alias for access_token. New CLI builds should persist access_token and refresh_token.
        key_id:
          type: string
          format: uuid
          description: Legacy alias for oauth_grant_id
        key_prefix:
          type: string
          description: Legacy display prefix derived from access_token
        access_token:
          type: string
          description: OAuth access token for CLI API authentication
        refresh_token:
          type: string
          description: OAuth refresh token used by the CLI to renew access
        token_type:
          type: string
          enum:
            - Bearer
        expires_in:
          type: integer
          description: Seconds until access_token expires
        auth_method:
          type: string
          enum:
            - oauth
        oauth_grant_id:
          type: string
          format: uuid
        oauth_client_id:
          type: string
        org_id:
          type: string
          format: uuid
        org_name:
          type:
            - string
            - "null"
        orgs:
          type: array
          items:
            $ref: "#/components/schemas/AgentOrgRef"
          description: Workspaces available to the verified email. The minted session targets `org_id`.
      required:
        - api_key
        - key_id
        - key_prefix
        - access_token
        - refresh_token
        - token_type
        - expires_in
        - auth_method
        - oauth_grant_id
        - oauth_client_id
        - org_id
        - org_name
        - orgs
    PlanLimits:
      type: object
      description: Plan-derived quota limits for an account.
      properties:
        storage_mb:
          type: number
        send_per_hour:
          type: number
        send_per_day:
          type: number
        api_per_minute:
          type: number
        webhooks_max_global:
          type:
            - number
            - "null"
        webhooks_per_domain:
          type: boolean
        filters_per_domain:
          type: boolean
        spam_thresholds_per_domain:
          type: boolean
      required:
        - storage_mb
        - send_per_hour
        - send_per_day
        - api_per_minute
        - webhooks_max_global
        - webhooks_per_domain
        - filters_per_domain
        - spam_thresholds_per_domain
    CreateAgentAccountInput:
      type: object
      additionalProperties: false
      properties:
        terms_accepted:
          type: boolean
          enum:
            - true
          description: Must be true to accept the Terms of Service and Privacy Policy.
        device_name:
          type: string
          minLength: 1
          maxLength: 80
          description: Optional label for the device or agent creating the account.
      required:
        - terms_accepted
    AgentAccountUpgradeHint:
      type: object
      description: In-band pointer to the upgrade path for an agent account.
      properties:
        plan:
          type: string
          enum:
            - developer
        description:
          type: string
        claim_path:
          type: string
      required:
        - plan
        - description
        - claim_path
    AgentAccountResult:
      type: object
      properties:
        api_key:
          type: string
          description: One-time API key (prefixed `prim_`). Shown once; store it securely.
        org_id:
          type: string
          format: uuid
        address:
          type:
            - string
            - "null"
          description: Provisioned managed inbox FQDN, or null if the inbox publish was deferred.
        plan:
          type: string
          enum:
            - agent
        limits:
          $ref: "#/components/schemas/PlanLimits"
        upgrade:
          $ref: "#/components/schemas/AgentAccountUpgradeHint"
      required:
        - api_key
        - org_id
        - address
        - plan
        - limits
        - upgrade
    StartAgentClaimInput:
      type: object
      additionalProperties: false
      properties:
        email:
          type: string
          format: email
          maxLength: 254
          description: Email to confirm. Must not already belong to a Primitive account.
      required:
        - email
    AgentClaimStartResult:
      type: object
      properties:
        claim_session_id:
          type: string
        resend_after_seconds:
          type: integer
        expires_in_seconds:
          type: integer
      required:
        - claim_session_id
        - resend_after_seconds
        - expires_in_seconds
    VerifyAgentClaimInput:
      type: object
      additionalProperties: false
      properties:
        verification_code:
          type: string
          minLength: 1
          maxLength: 32
          description: The verification code emailed by the claim start step.
      required:
        - verification_code
    AgentClaimResult:
      type: object
      properties:
        org_id:
          type: string
          format: uuid
        plan:
          type: string
          enum:
            - developer
        email:
          type: string
          format: email
        limits:
          $ref: "#/components/schemas/PlanLimits"
      required:
        - org_id
        - plan
        - email
        - limits
    CreateAgentClaimLinkInput:
      type: object
      additionalProperties: false
      description: No fields; an empty object is accepted.
      properties: {}
    AgentClaimLinkResult:
      type: object
      properties:
        claim_token:
          type: string
        claim_url:
          type:
            - string
            - "null"
          description: Browser URL to hand to a human, or null if no web origin is configured.
        expires_in_seconds:
          type: integer
      required:
        - claim_token
        - claim_url
        - expires_in_seconds
    CliLogoutInput:
      type: object
      additionalProperties: false
      properties:
        key_id:
          type: string
          format: uuid
          description: Optional id guard; when provided it must match the authenticated OAuth grant id or API key id
    CliLogoutResult:
      type: object
      properties:
        revoked:
          type: boolean
          description: True when an OAuth grant was revoked. False for API-key-authenticated legacy logout, which only clears local CLI state.
        key_id:
          type: string
          format: uuid
          description: API key id for API-key-authenticated legacy logout
        oauth_grant_id:
          type: string
          format: uuid
          description: OAuth grant id revoked by OAuth-authenticated logout
      required:
        - revoked
    Account:
      type: object
      properties:
        id:
          type: string
          format: uuid
        email:
          type: string
        plan:
          type: string
        limits:
          $ref: "#/components/schemas/PlanLimits"
        entitlements:
          type: array
          items:
            type: string
          description: |
            Granted org entitlement keys (sorted). A headless caller reads its
            capabilities here — e.g. an emailless agent seeing only
            ["send_mail", "send_to_known_addresses"] knows it is reply-only.
        managed_inbox_address:
          type:
            - string
            - "null"
          description: The managed inbox FQDN to reply as, or null if the org has no managed inbox.
        created_at:
          type: string
          format: date-time
        onboarding_completed:
          type: boolean
        onboarding_step:
          type:
            - string
            - "null"
        stripe_subscription_status:
          type:
            - string
            - "null"
        subscription_current_period_end:
          type:
            - string
            - "null"
          format: date-time
        subscription_cancel_at_period_end:
          type:
            - boolean
            - "null"
        spam_threshold:
          type:
            - number
            - "null"
          minimum: 0
          maximum: 15
        discard_content_on_webhook_confirmed:
          type: boolean
        webhook_secret_rotated_at:
          type:
            - string
            - "null"
          format: date-time
      required:
        - id
        - email
        - plan
        - limits
        - entitlements
        - managed_inbox_address
        - created_at
        - discard_content_on_webhook_confirmed
    AccountUpdated:
      type: object
      properties:
        id:
          type: string
          format: uuid
        email:
          type: string
        plan:
          type: string
        spam_threshold:
          type:
            - number
            - "null"
          minimum: 0
          maximum: 15
        discard_content_on_webhook_confirmed:
          type: boolean
      required:
        - id
        - email
        - plan
        - discard_content_on_webhook_confirmed
    UpdateAccountInput:
      type: object
      additionalProperties: false
      properties:
        spam_threshold:
          type:
            - number
            - "null"
          minimum: 0
          maximum: 15
          description: Global spam score threshold (0-15). Emails scoring above this are rejected. Set to null to disable.
        discard_content_on_webhook_confirmed:
          type: boolean
          description: Whether to discard email content after the webhook endpoint confirms receipt.
      minProperties: 1
    StorageStats:
      type: object
      properties:
        used_bytes:
          type: integer
          description: Total storage used in bytes
        used_kb:
          type: number
          description: Total storage used in kilobytes (1 decimal)
        used_mb:
          type: number
          description: Total storage used in megabytes (2 decimals)
        quota_mb:
          type: number
          description: Storage quota in megabytes (based on plan)
        percentage:
          type: number
          description: Percentage of quota used (1 decimal)
        emails_count:
          type: integer
          description: Number of stored emails
      required:
        - used_bytes
        - used_kb
        - used_mb
        - quota_mb
        - percentage
        - emails_count
    WebhookSecret:
      type: object
      properties:
        secret:
          type: string
          description: The webhook signing secret value
      required:
        - secret
    InboxStatus:
      type: object
      additionalProperties: false
      properties:
        ready:
          type: boolean
          description: True when an active inbound domain and at least one processing route are both ready.
        receiving_ready:
          type: boolean
          description: True when at least one active verified or managed domain can receive mail.
        processing_ready:
          type: boolean
          description: True when at least one receiving-ready domain has an enabled webhook or function route.
        summary:
          type: string
        next_actions:
          type: array
          items:
            $ref: "#/components/schemas/InboxStatusNextAction"
        domains:
          type: array
          items:
            $ref: "#/components/schemas/InboxStatusDomain"
        endpoints:
          type: object
          additionalProperties: false
          properties:
            total:
              type: integer
            enabled:
              type: integer
            disabled:
              type: integer
            fallback_enabled:
              type: integer
            domain_scoped_enabled:
              type: integer
            http_enabled:
              type: integer
            function_enabled:
              type: integer
          required:
            - total
            - enabled
            - disabled
            - fallback_enabled
            - domain_scoped_enabled
            - http_enabled
            - function_enabled
        functions:
          type: object
          additionalProperties: false
          properties:
            total:
              type: integer
            deployed:
              type: integer
            pending:
              type: integer
            failed:
              type: integer
          required:
            - total
            - deployed
            - pending
            - failed
        recent_emails:
          type: object
          description: Inbound email activity from the last 30 days.
          additionalProperties: false
          properties:
            total:
              type: integer
              description: Number of inbound emails received in the last 30 days.
            latest_received_at:
              type:
                - string
                - "null"
              format: date-time
              description: Most recent inbound email received in the last 30 days.
          required:
            - total
            - latest_received_at
      required:
        - ready
        - receiving_ready
        - processing_ready
        - summary
        - next_actions
        - domains
        - endpoints
        - functions
        - recent_emails
    InboxStatusNextAction:
      type: object
      additionalProperties: false
      properties:
        kind:
          type: string
          enum:
            - add_domain
            - verify_domain
            - configure_processing
            - send_test_email
            - fix_failed_functions
        message:
          type: string
          description: Human-readable next step.
        command:
          type: string
          description: Suggested Primitive CLI command when there is an obvious next step.
      required:
        - kind
        - message
    InboxStatusDomain:
      type: object
      additionalProperties: false
      properties:
        id:
          type: string
        domain:
          type: string
        verified:
          type: boolean
        active:
          type: boolean
        managed:
          type: boolean
        receiving_ready:
          type: boolean
        processing_ready:
          type: boolean
        processing_route_count:
          type: integer
        endpoint_count:
          type: integer
        enabled_endpoint_count:
          type: integer
        function_endpoint_count:
          type: integer
        email_count:
          type: integer
          description: Number of inbound emails received for this domain in the last 30 days.
        latest_email_received_at:
          type:
            - string
            - "null"
          format: date-time
          description: Most recent inbound email received for this domain in the last 30 days.
        status:
          type: string
          enum:
            - ready
            - stored_only
            - pending_dns
            - inactive
      required:
        - id
        - domain
        - verified
        - active
        - managed
        - receiving_ready
        - processing_ready
        - processing_route_count
        - endpoint_count
        - enabled_endpoint_count
        - function_endpoint_count
        - email_count
        - latest_email_received_at
        - status
    InboxStatusEndpointSummary:
      type: object
      additionalProperties: false
      properties:
        total:
          type: integer
        enabled:
          type: integer
        disabled:
          type: integer
        fallback_enabled:
          type: integer
        domain_scoped_enabled:
          type: integer
        http_enabled:
          type: integer
        function_enabled:
          type: integer
      required:
        - total
        - enabled
        - disabled
        - fallback_enabled
        - domain_scoped_enabled
        - http_enabled
        - function_enabled
    InboxStatusFunctionSummary:
      type: object
      additionalProperties: false
      properties:
        total:
          type: integer
        deployed:
          type: integer
        pending:
          type: integer
        failed:
          type: integer
      required:
        - total
        - deployed
        - pending
        - failed
    InboxStatusRecentEmailSummary:
      type: object
      description: Inbound email activity from the last 30 days.
      additionalProperties: false
      properties:
        total:
          type: integer
          description: Number of inbound emails received in the last 30 days.
        latest_received_at:
          type:
            - string
            - "null"
          format: date-time
          description: Most recent inbound email received in the last 30 days.
      required:
        - total
        - latest_received_at
    Domain:
      description: |
        A domain can be either verified or unverified. Verified domains have
        `is_active` and `spam_threshold` fields. Unverified domains have a
        `verification_token` and `dns_records` for DNS setup.
      oneOf:
        - $ref: "#/components/schemas/VerifiedDomain"
        - $ref: "#/components/schemas/UnverifiedDomain"
    VerifiedDomain:
      type: object
      properties:
        id:
          type: string
          format: uuid
        org_id:
          type: string
          format: uuid
        domain:
          type: string
        verified:
          type: boolean
          const: true
        is_active:
          type: boolean
        spam_threshold:
          type:
            - number
            - "null"
          minimum: 0
          maximum: 15
        verification_token:
          type:
            - string
            - "null"
        created_at:
          type: string
          format: date-time
      required:
        - id
        - org_id
        - domain
        - verified
        - is_active
        - created_at
    DomainDnsRecord:
      type: object
      additionalProperties: false
      properties:
        type:
          type: string
          enum:
            - MX
            - TXT
          description: DNS record type.
        name:
          type: string
          description: DNS-provider host/name value relative to the managed root zone.
        fqdn:
          type: string
          description: Fully-qualified DNS record name.
        value:
          type: string
          description: Exact value to publish.
        priority:
          type: integer
          description: MX priority. Present only for MX records.
        ttl:
          type: integer
          description: Suggested TTL in seconds when the API can provide one.
        required:
          type: boolean
          const: true
        purpose:
          type: string
          enum:
            - inbound_mx
            - ownership_verification
            - spf
            - dkim
            - dmarc
            - tls_reporting
        status:
          type: string
          enum:
            - pending
            - found
            - missing
            - incorrect
        message:
          type: string
          description: Short explanation of why this record is needed.
      required:
        - type
        - name
        - fqdn
        - value
        - required
        - purpose
        - status
    UnverifiedDomain:
      type: object
      properties:
        id:
          type: string
          format: uuid
        org_id:
          type: string
          format: uuid
        domain:
          type: string
        verified:
          type: boolean
          const: false
        verification_token:
          type: string
          description: Add this value as a TXT record to verify ownership
        dns_records:
          type: array
          description: Exact DNS records to publish for a pending domain claim or verification attempt.
          items:
            $ref: "#/components/schemas/DomainDnsRecord"
        created_at:
          type: string
          format: date-time
      required:
        - id
        - org_id
        - domain
        - verified
        - verification_token
        - created_at
    AddDomainInput:
      type: object
      additionalProperties: false
      properties:
        domain:
          type: string
          minLength: 1
          maxLength: 253
          description: The domain name to claim (e.g. "example.com")
        confirmed:
          type: boolean
          description: Set to true to confirm replacing an existing mailbox provider after an mx_conflict response.
        outbound:
          type: boolean
          deprecated: true
          description: Deprecated and ignored. Outbound DNS is provisioned for every new domain claim.
      required:
        - domain
    UpdateDomainInput:
      type: object
      additionalProperties: false
      properties:
        is_active:
          type: boolean
          description: Whether the domain accepts incoming emails
        spam_threshold:
          type:
            - number
            - "null"
          minimum: 0
          maximum: 15
          description: Per-domain spam threshold override (Pro plan required)
      minProperties: 1
    DomainVerifyResult:
      oneOf:
        - type: object
          properties:
            verified:
              type: boolean
              const: true
            dns_records:
              type: array
              description: Exact DNS records to publish for a pending domain claim or verification attempt.
              items:
                $ref: "#/components/schemas/DomainDnsRecord"
          required:
            - verified
        - type: object
          properties:
            verified:
              type: boolean
              const: false
            mxFound:
              type: boolean
              description: Whether MX records point to Primitive
            txtFound:
              type: boolean
              description: Whether the TXT verification record was found
            spfFound:
              type: boolean
              description: Whether the SPF record includes Primitive.
            dkimFound:
              type: boolean
              description: Whether the DKIM public key record was found.
            dmarcFound:
              type: boolean
              description: Whether the DMARC record was found.
            tlsRptFound:
              type: boolean
              description: Whether the TLS-RPT record was found.
            dns_records:
              type: array
              description: Exact DNS records to publish for a pending domain claim or verification attempt.
              items:
                $ref: "#/components/schemas/DomainDnsRecord"
            error:
              type: string
              description: Human-readable verification failure reason
          required:
            - verified
            - mxFound
            - txtFound
            - error
    EmailSummary:
      type: object
      properties:
        id:
          type: string
          format: uuid
        message_id:
          type:
            - string
            - "null"
        domain_id:
          type:
            - string
            - "null"
          format: uuid
        org_id:
          type:
            - string
            - "null"
          format: uuid
        status:
          $ref: "#/components/schemas/EmailStatus"
        sender:
          type: string
          description: |
            SMTP envelope sender (return-path) the inbound mail server
            accepted. For most legitimate mail this equals the bare
            address in the From header; for mailing lists, bounce
            handlers, and forwarders it is typically the bounce address
            rather than the human-visible sender.

            For the parsed From-header value (with display name handling
            and a sender-fallback when the header is unparseable), GET
            the email by id and use `from_email`.
        recipient:
          type: string
        subject:
          type:
            - string
            - "null"
        domain:
          type: string
        spam_score:
          type:
            - number
            - "null"
        created_at:
          type: string
          format: date-time
        received_at:
          type: string
          format: date-time
        raw_size_bytes:
          type:
            - integer
            - "null"
        webhook_status:
          $ref: "#/components/schemas/EmailWebhookStatus"
        webhook_attempt_count:
          type: integer
        thread_id:
          type:
            - string
            - "null"
          format: uuid
          description: |
            Conversation thread this message belongs to. Fetch
            `/threads/{thread_id}` for the full ordered thread. NULL on
            messages received before threading was enabled.
      required:
        - id
        - status
        - sender
        - recipient
        - domain
        - created_at
        - received_at
        - webhook_attempt_count
    EmailSearchHighlights:
      type: object
      properties:
        subject:
          type: array
          items:
            type: string
          description: Subject snippets with matching terms highlighted.
        body:
          type: array
          items:
            type: string
          description: Body snippets with matching terms highlighted.
      required:
        - subject
        - body
    EmailSearchResult:
      allOf:
        - $ref: "#/components/schemas/EmailSummary"
        - type: object
          properties:
            attachment_count:
              type: integer
              description: Number of parsed attachments on the email.
            from_known_address:
              type: boolean
              description: Whether the parsed From address is known to this org from prior authenticated inbound mail.
            score:
              type: number
              description: Relevance score. Present only when sorting by relevance.
            highlights:
              $ref: "#/components/schemas/EmailSearchHighlights"
          required:
            - attachment_count
            - from_known_address
    EmailSearchMeta:
      type: object
      properties:
        total:
          type: integer
          description: Total number of matching records, capped when `total_capped` is true.
        total_capped:
          type: boolean
          description: Whether `total` was capped instead of counted exactly.
        limit:
          type: integer
          description: Page size used for this request.
        cursor:
          type:
            - string
            - "null"
          description: Cursor for the next search page, or null if no more results.
        sort:
          type: string
          enum:
            - relevance
            - received_at_desc
            - received_at_asc
          description: Sort mode used for the result page.
      required:
        - total
        - total_capped
        - limit
        - cursor
        - sort
    EmailSearchFacetBucket:
      type: object
      properties:
        value:
          type:
            - string
            - "null"
        count:
          type: integer
      required:
        - value
        - count
    EmailSearchFacets:
      type: object
      properties:
        by_sender:
          type: array
          items:
            $ref: "#/components/schemas/EmailSearchFacetBucket"
        by_domain:
          type: array
          items:
            $ref: "#/components/schemas/EmailSearchFacetBucket"
        by_status:
          type: array
          items:
            $ref: "#/components/schemas/EmailSearchFacetBucket"
        has_attachment:
          type: object
          properties:
            "true":
              type: integer
            "false":
              type: integer
          required:
            - "true"
            - "false"
      required:
        - by_sender
        - by_domain
        - by_status
        - has_attachment
    EmailDetail:
      type: object
      properties:
        id:
          type: string
          format: uuid
        message_id:
          type:
            - string
            - "null"
        domain_id:
          type:
            - string
            - "null"
          format: uuid
        org_id:
          type:
            - string
            - "null"
          format: uuid
        sender:
          type: string
          description: |
            SMTP envelope sender (return-path) the inbound mail server
            accepted. Same value as `smtp_mail_from`; both fields exist
            so protocol-aware tooling can use whichever name it expects.

            For most legitimate mail this equals `from_email`; for
            mailing lists, bounce handlers, and forwarders it is
            typically the bounce-handling address rather than the
            human-visible sender.

            **For the canonical "who sent this email" value, use
            `from_email`.**
        recipient:
          type: string
        subject:
          type:
            - string
            - "null"
        body_text:
          type:
            - string
            - "null"
          description: Plain-text body parsed from the inbound MIME, matching the `email.parsed.body_text` field on the webhook payload. Null when the message had no text part or parsing failed.
        body_html:
          type:
            - string
            - "null"
          description: HTML body parsed from the inbound MIME, matching the `email.parsed.body_html` field on the webhook payload. Null when the message had no HTML part or parsing failed.
        status:
          $ref: "#/components/schemas/EmailStatus"
        domain:
          type: string
        spam_score:
          type:
            - number
            - "null"
        raw_size_bytes:
          type:
            - integer
            - "null"
        raw_sha256:
          type:
            - string
            - "null"
        created_at:
          type: string
          format: date-time
        received_at:
          type: string
          format: date-time
        rejection_reason:
          type:
            - string
            - "null"
        webhook_status:
          $ref: "#/components/schemas/EmailWebhookStatus"
        webhook_attempt_count:
          type: integer
        webhook_last_attempt_at:
          type:
            - string
            - "null"
          format: date-time
        webhook_last_status_code:
          type:
            - integer
            - "null"
        webhook_last_error:
          type:
            - string
            - "null"
        webhook_fired_at:
          type:
            - string
            - "null"
          format: date-time
        smtp_helo:
          type:
            - string
            - "null"
        smtp_mail_from:
          type:
            - string
            - "null"
          description: |
            SMTP envelope MAIL FROM (return-path), as accepted by the
            inbound mail server. Same value as `sender`; both fields
            exist so protocol-aware tooling can use whichever name it
            expects.

            For the canonical "who sent this email" value (display name
            stripped, From-header preferred), use `from_email`.
        smtp_rcpt_to:
          type:
            - array
            - "null"
          items:
            type: string
        from_header:
          type:
            - string
            - "null"
          description: |
            Raw `From:` header from the message body, including any
            display name (e.g. `"Alice Example" <alice@example.com>`).
            Use this when you need the display name for rendering.

            For the bare email address (display name stripped), use
            `from_email`.
        content_discarded_at:
          type:
            - string
            - "null"
          format: date-time
        content_discarded_by_delivery_id:
          type:
            - string
            - "null"
        from_email:
          type: string
          description: |
            Bare email address parsed from the `From:` header, with
            display name stripped (e.g. `alice@example.com`). Falls
            back to `sender` (the SMTP envelope MAIL FROM) when the
            `From:` header cannot be parsed.

            **This is the canonical "who sent this email" field for
            most use cases**, including comparing against allowlists,
            routing replies, or displaying the sender to a user. Use
            `from_header` when you specifically need the display name,
            or `sender`/`smtp_mail_from` when you need the SMTP
            envelope value (e.g. to follow a bounce).
        to_email:
          type: string
          description: Parsed to address (same as recipient)
        from_known_address:
          type: boolean
          description: |
            True when the inbound's sender address has a matching grant
            in the org's known-send-addresses list. Advisory: a true
            value does not by itself guarantee that a reply will be
            accepted by send-mail's gates; the per-send check at send
            time remains authoritative.
        replies:
          type: array
          description: |
            Sent emails recorded as replies to this inbound, in send
            order (ascending). Populated when a customer's send-mail
            request carries an `in_reply_to` Message-ID that matches
            this inbound's `message_id` in the same org. Includes
            attempts that were gate-denied, so the array reflects every
            recorded reply attempt regardless of outcome.
          items:
            $ref: "#/components/schemas/EmailDetailReply"
        reply_to_sent_email_id:
          type:
            - string
            - "null"
          format: uuid
          description: |
            The `sent_emails.id` of the outbound this inbound was a
            reply to, when resolvable. Set at inbound ingest by
            matching the parsed In-Reply-To (or References, as a
            fallback) against `sent_emails.message_id` in the same
            org. The mirror of `sent_emails.in_reply_to_email_id` for
            the inbound side of a thread. NULL when the inbound is
            not a threaded reply to one of your sends, when neither
            header survived the path through intermediate MTAs, or on
            inbound received before this auto-link landed.
        thread_id:
          type:
            - string
            - "null"
          format: uuid
          description: |
            Conversation thread this message belongs to. Inbound and
            outbound messages in the same conversation share a
            `thread_id`; fetch `/threads/{thread_id}` for the full
            ordered thread. Assigned at ingest. NULL on messages
            received before threading was enabled (until backfilled).
        parsed:
          allOf:
            - $ref: "#/components/schemas/ParsedEmailData"
          description: |
            Parsed MIME content (addresses, threading headers,
            attachment metadata), matching the `email.parsed` object
            on the webhook payload so one parser handles both the
            webhook and this endpoint. The top-level `body_text` /
            `body_html` fields above are the same values as
            `parsed.body_text` / `parsed.body_html`, retained for
            backward compatibility.
        auth:
          allOf:
            - $ref: "#/components/schemas/EmailAuth"
          description: |
            SPF / DKIM / DMARC verdicts computed at ingest, matching
            the `email.auth` object on the webhook payload. Use these
            to decide how much to trust a message before acting on
            instructions it contains.
      required:
        - id
        - sender
        - recipient
        - status
        - domain
        - created_at
        - received_at
        - webhook_attempt_count
        - from_email
        - to_email
        - replies
        - parsed
        - auth
    EmailDetailReply:
      type: object
      properties:
        id:
          type: string
          format: uuid
          description: Sent-email row id.
        status:
          $ref: "#/components/schemas/SentEmailStatus"
        to_address:
          type: string
          description: Recipient address as recorded on the sent_emails row.
        subject:
          type:
            - string
            - "null"
        created_at:
          type: string
          format: date-time
        queue_id:
          type:
            - string
            - "null"
          description: Outbound relay queue identifier when available.
      required:
        - id
        - status
        - to_address
        - created_at
    EmailAddress:
      type: object
      description: A parsed RFC 5322 address with optional display name.
      properties:
        name:
          type:
            - string
            - "null"
          description: Display name, when present (e.g. `Alice Example`).
        address:
          type: string
          description: Bare email address (e.g. `alice@example.com`).
      required:
        - address
    EmailAttachment:
      type: object
      description: |
        Metadata for one attachment. The bytes are not inline; download
        all attachments for a message as a gzipped tarball via
        `/emails/{id}/attachments.tar.gz`. `sha256` lets you verify a
        specific part after extraction.
      properties:
        filename:
          type:
            - string
            - "null"
        content_type:
          type:
            - string
            - "null"
        size_bytes:
          type: integer
        sha256:
          type:
            - string
            - "null"
        part_index:
          type: integer
          description: Zero-based index of this part within the message.
      required:
        - size_bytes
    ParsedEmailData:
      type: object
      description: |
        Parsed MIME content for an inbound email. Mirrors the
        `email.parsed` object on the webhook payload so a single parser
        handles both surfaces. `status` is `complete` when parsing
        succeeded; on `failed` the body/address/attachment fields are
        absent and `error` describes why.
      properties:
        status:
          type: string
          enum:
            - complete
            - failed
        body_text:
          type:
            - string
            - "null"
          description: Plain-text body. Present when `status` is `complete`.
        body_html:
          type:
            - string
            - "null"
          description: HTML body. Present when `status` is `complete`.
        reply_to:
          type:
            - array
            - "null"
          items:
            $ref: "#/components/schemas/EmailAddress"
          description: Parsed `Reply-To` header addresses.
        cc:
          type:
            - array
            - "null"
          items:
            $ref: "#/components/schemas/EmailAddress"
          description: Parsed `Cc` header addresses.
        bcc:
          type:
            - array
            - "null"
          items:
            $ref: "#/components/schemas/EmailAddress"
          description: Parsed `Bcc` header addresses (rarely present on inbound).
        to_addresses:
          type:
            - array
            - "null"
          items:
            $ref: "#/components/schemas/EmailAddress"
          description: Parsed `To` header addresses.
        in_reply_to:
          type:
            - array
            - "null"
          items:
            type: string
          description: Message-IDs from the `In-Reply-To` header.
        references:
          type:
            - array
            - "null"
          items:
            type: string
          description: Message-IDs from the `References` header.
        attachments:
          type: array
          items:
            $ref: "#/components/schemas/EmailAttachment"
          description: Attachment metadata. Empty array when none.
        error:
          type:
            - object
            - "null"
          description: |
            Present (non-null) only when `status` is `failed`. When
            present, all three fields are populated, so a consumer can
            branch on `code` without defensive null checks.
          properties:
            code:
              type: string
              description: Stable failure code (e.g. `PARSE_FAILED`).
            message:
              type: string
            retryable:
              type: boolean
          required:
            - code
            - message
            - retryable
      required:
        - status
    DkimSignature:
      type: object
      description: One DKIM signature found on the message, with its verdict.
      properties:
        domain:
          type: string
        selector:
          type: string
        result:
          type: string
          description: Verification result (e.g. `pass`, `fail`, `none`).
        aligned:
          type: boolean
          description: Whether the signing domain aligns with the From domain (for DMARC).
        keyBits:
          type:
            - integer
            - "null"
        algo:
          type:
            - string
            - "null"
      required:
        - domain
        - selector
        - result
        - aligned
    EmailAuth:
      type: object
      description: |
        SPF / DKIM / DMARC verdicts computed at ingest. Mirrors the
        `email.auth` object on the webhook payload. Field names are
        camelCase to match that payload exactly. For messages received
        before auth was recorded, the verdicts default to `none`.
      properties:
        spf:
          type: string
          description: SPF result (e.g. `pass`, `fail`, `softfail`, `none`).
        dmarc:
          type: string
          description: DMARC result (e.g. `pass`, `fail`, `none`).
        dmarcPolicy:
          type:
            - string
            - "null"
          description: Published DMARC policy (`none`, `quarantine`, `reject`).
        dmarcFromDomain:
          type:
            - string
            - "null"
          description: The From-header domain DMARC was evaluated against.
        dmarcSpfAligned:
          type: boolean
        dmarcDkimAligned:
          type: boolean
        dmarcSpfStrict:
          type:
            - boolean
            - "null"
        dmarcDkimStrict:
          type:
            - boolean
            - "null"
        dkimSignatures:
          type: array
          items:
            $ref: "#/components/schemas/DkimSignature"
      required:
        - spf
        - dmarc
        - dmarcSpfAligned
        - dmarcDkimAligned
        - dkimSignatures
    Thread:
      type: object
      description: |
        A conversation thread: its metadata plus the inbound and
        outbound messages that belong to it, interleaved oldest-first.
        Membership is the stored `thread_id` on each message. Bodies are
        omitted here to keep the thread view lightweight; fetch
        `/emails/{id}` or `/sent-emails/{id}` for a single message's
        full content.
      properties:
        id:
          type: string
          format: uuid
        subject:
          type:
            - string
            - "null"
          description: Normalized subject of the thread (Re/Fwd prefixes stripped).
        root_message_id:
          type:
            - string
            - "null"
          description: Message-ID of the conversation root, when known.
        message_count:
          type: integer
          description: |
            Total messages in the thread. `messages` is capped (most
            recent first, then re-sorted oldest-first), so
            `message_count > messages.length` signals truncation.
        first_message_at:
          type:
            - string
            - "null"
          format: date-time
        last_message_at:
          type:
            - string
            - "null"
          format: date-time
        created_at:
          type: string
          format: date-time
        messages:
          type: array
          items:
            $ref: "#/components/schemas/ThreadMessage"
      required:
        - id
        - message_count
        - created_at
        - messages
    ThreadMessage:
      type: object
      description: One message in a thread (inbound or outbound).
      properties:
        direction:
          type: string
          enum:
            - inbound
            - outbound
          description: |
            `inbound` for a received email (`/emails/{id}`), `outbound`
            for a send (`/sent-emails/{id}`). Use it with `id` to fetch
            full content from the right endpoint.
        id:
          type: string
          format: uuid
        message_id:
          type:
            - string
            - "null"
        from:
          type:
            - string
            - "null"
        to:
          type:
            - string
            - "null"
        subject:
          type:
            - string
            - "null"
        status:
          type:
            - string
            - "null"
          description: Lifecycle status (an EmailStatus or SentEmailStatus value, per `direction`).
        timestamp:
          type:
            - string
            - "null"
          format: date-time
          description: received_at for inbound, created_at for outbound.
      required:
        - direction
        - id
    Conversation:
      type: object
      description: |
        The full conversation an inbound email belongs to, as ordered,
        ready-to-prompt turns with bodies. Resolves the thread from the
        email and returns every message oldest-first, so an agent that
        received an email can pass `messages` straight to a chat model in
        one call.
      properties:
        thread_id:
          type:
            - string
            - "null"
          format: uuid
          description: |
            The thread this email belongs to, or null when the email
            isn't threaded yet (the conversation is then just this one
            message).
        subject:
          type:
            - string
            - "null"
          description: |
            Normalized thread subject (Re/Fwd prefixes stripped), or the
            email's own subject when it isn't threaded.
        message_count:
          type: integer
          description: |
            Total messages in the thread. `messages` is capped, so
            `truncated` is true (and this can exceed `messages.length`)
            when older messages were omitted.
        truncated:
          type: boolean
          description: |
            True when `messages` omits part of the conversation because
            the thread exceeds the per-call cap.
        messages:
          type: array
          items:
            $ref: "#/components/schemas/ConversationMessage"
      required:
        - thread_id
        - message_count
        - truncated
        - messages
    ConversationMessage:
      type: object
      description: One message in the conversation, with its body and a chat role.
      properties:
        role:
          type: string
          enum:
            - user
            - assistant
          description: |
            Chat role derived from `direction`: `user` for inbound
            (received) messages, `assistant` for outbound (your own prior
            replies). Lets `messages` be passed directly to a chat model.
        direction:
          type: string
          enum:
            - inbound
            - outbound
          description: |
            `inbound` for a received email (`/emails/{id}`), `outbound`
            for a send (`/sent-emails/{id}`).
        id:
          type: string
          format: uuid
        message_id:
          type:
            - string
            - "null"
        from:
          type:
            - string
            - "null"
        to:
          type:
            - string
            - "null"
        subject:
          type:
            - string
            - "null"
        text:
          type: string
          description: |
            Plain-text body. Empty string when the message has no text
            part or its content was discarded by retention.
        timestamp:
          type:
            - string
            - "null"
          format: date-time
          description: received_at for inbound, created_at for outbound.
      required:
        - role
        - direction
        - id
        - text
    SendMailAttachment:
      type: object
      additionalProperties: false
      properties:
        filename:
          type: string
          minLength: 1
          maxLength: 255
          description: Attachment filename. Control characters are rejected.
        content_type:
          type: string
          minLength: 1
          maxLength: 255
          description: Optional MIME content type. Control characters are rejected.
        content_base64:
          type: string
          minLength: 1
          maxLength: 44040192
          description: Base64-encoded attachment bytes.
      required:
        - filename
        - content_base64
    SendMailInput:
      type: object
      additionalProperties: false
      properties:
        from:
          type: string
          minLength: 3
          maxLength: 998
          description: RFC 5322 From header. The sender domain must be a verified outbound domain for your organization.
        to:
          type: string
          minLength: 3
          maxLength: 320
          description: Recipient address. Recipient eligibility depends on your account's outbound entitlements.
        subject:
          type: string
          minLength: 1
          maxLength: 998
          description: Subject line for the outbound message
        body_text:
          type: string
          description: Plain-text message body. At least one of body_text or body_html is required. The combined UTF-8 byte length of body_text and body_html must be at most 262144 bytes.
        body_html:
          type: string
          description: HTML message body. At least one of body_text or body_html is required. The combined UTF-8 byte length of body_text and body_html must be at most 262144 bytes.
        in_reply_to:
          type: string
          minLength: 1
          maxLength: 998
          pattern: ^[^\x00-\x1F\x7F]+$
          description: Message-ID of the direct parent email when sending a threaded reply.
        references:
          type: array
          maxItems: 100
          description: Full ordered message-id chain for the thread.
          items:
            type: string
            minLength: 1
            maxLength: 998
            pattern: ^[^\x00-\x1F\x7F]+$
        attachments:
          type: array
          maxItems: 100
          description: Inline attachments. Send requests with attachments to https://api.primitive.dev/v1/send-mail. Combined raw decoded attachment bytes must be at most 31457280.
          items:
            $ref: "#/components/schemas/SendMailAttachment"
        wait:
          type: boolean
          description: When true, wait for the first downstream SMTP delivery outcome before returning.
        wait_timeout_ms:
          type: integer
          minimum: 1000
          maximum: 30000
          description: Maximum time to wait for a delivery outcome when wait is true. Defaults to 30000.
      required:
        - from
        - to
        - subject
    EmailStatus:
      type: string
      description: |
        Lifecycle status of an INBOUND email (a row in the `emails`
        table). Distinct from `SentEmailStatus`, which describes
        the OUTBOUND lifecycle (the `sent_emails` table) and uses
        a different vocabulary because the lifecycles differ.
        Possible values:

          - `pending`: the row was inserted at ingestion (mx_main)
            and has not yet completed the spam / filter / auth
            pipeline. Body and parsed fields are present; webhook
            delivery is not yet scheduled. Most rows transition out
            of `pending` within seconds.
          - `accepted`: the inbound passed the policy gates and is
            queued for webhook delivery. The `webhook_status` field
            tracks the separate webhook-delivery lifecycle from
            this point.
          - `completed`: terminal success. Webhook delivery
            attempted and acknowledged by every active endpoint, OR
            no endpoints are configured, so the row is durably
            archived.
          - `rejected`: terminal failure at ingestion (spam, blocked
            sender, filter rule, malformed). The body and metadata
            are stored for auditing but no webhook fires and the
            row is not repliable.

        See also `webhook_status` (separate enum tracking the
        webhook-delivery state machine) and `SentEmailStatus` (the
        outbound vocabulary).
      enum:
        - pending
        - accepted
        - completed
        - rejected
    EmailWebhookStatus:
      type:
        - string
        - "null"
      description: |
        Webhook-delivery state for an inbound email. Tracks a
        SEPARATE lifecycle from the email's `status` field; the
        same row carries both. Possible values:

          - `pending`: ingestion is past `pending` (the email itself
            is `accepted`) but the webhook fan-out has not yet
            started for this row.
          - `in_flight`: at least one delivery attempt is in flight.
          - `fired`: terminal success. Every active endpoint
            acknowledged the delivery (or accepted it after retries).
          - `failed`: terminal partial-failure. At least one endpoint
            exhausted its retry budget; some endpoints may still
            have succeeded.
          - `exhausted`: terminal failure. Every endpoint exhausted
            its retry budget without success.
          - `null`: no endpoints configured, so no webhook lifecycle
            applies.

        Note that the value `pending` here does NOT mean the email
        is `pending`; it means the email is past ingestion but
        webhook delivery has not yet begun. Two overlapping uses
        of the word `pending` for distinct lifecycle phases.
      enum:
        - pending
        - in_flight
        - fired
        - failed
        - exhausted
        - null
    SentEmailStatus:
      type: string
      description: |
        Lifecycle status of a sent_emails row. Possible values:

          - `queued`: pre-call INSERT; the outbound agent has not
            yet replied.
          - `submitted_to_agent`: agent accepted; `queue_id` is set.
          - `agent_failed`: agent rejected; `error_code` and
            `error_message` carry the reason.
          - `gate_denied`: a recipient-scope gate denied the send;
            the agent was never called. The `gates` array carries
            the denial detail. /send-mail returns 403 in this case
            so callers see the denial synchronously; /sent-emails
            additionally records the row for historical lookup,
            which is when this status appears in a listing.
          - `unknown`: terminal indeterminate; the on-box log
            poller couldn't classify the receiver's response.
          - `delivered` / `bounced` / `deferred` / `wait_timeout`:
            terminal delivery outcomes (see DeliveryStatus).
      enum:
        - queued
        - submitted_to_agent
        - agent_failed
        - gate_denied
        - unknown
        - delivered
        - bounced
        - deferred
        - wait_timeout
    DeliveryStatus:
      type: string
      description: |
        Narrower enum covering only the four terminal delivery
        outcomes returned to a synchronous `wait: true` send.

        On the SendMailResult shape, `delivery_status` is always
        equal to `status` whenever both are present (i.e. on
        terminal-state replays and live wait=true responses).
        The two fields exist so callers that want to type-narrow
        on "this is a delivery outcome" can pattern-match against
        the four-value enum without handling the broader
        SentEmailStatus value set (which also covers `queued`,
        `submitted_to_agent`, `agent_failed`, `gate_denied`,
        `unknown`).

        On async-mode and pre-terminal responses, `delivery_status`
        is absent and only `status` is populated. Use `status` if
        you want a single field that's always present.
      enum:
        - delivered
        - bounced
        - deferred
        - wait_timeout
    SentEmailSummary:
      type: object
      description: |
        List-row projection of a sent-email record. Drops
        `body_text` and `body_html` to keep paginated responses
        small; fetch /sent-emails/{id} for the full record with
        bodies.
      properties:
        id:
          type: string
          format: uuid
        status:
          $ref: "#/components/schemas/SentEmailStatus"
        status_changed_at:
          type: string
          format: date-time
          description: |
            Timestamp of the most recent status transition.
            Polling clients should treat `status='queued'` AND
            `status_changed_at` older than 5 minutes as
            "stuck-queued" (the post-tx UPDATE failed and the
            actual delivery state is recoverable from on-box logs
            via `queue_id` when populated, or `request_id`).
        created_at:
          type: string
          format: date-time
        updated_at:
          type: string
          format: date-time
        client_idempotency_key:
          type:
            - string
            - "null"
          description: |
            Effective idempotency key used for this send. If the
            caller passed the `Idempotency-Key` header, this is
            that value; otherwise it's a server-derived hash of
            the canonical request payload.
        content_hash:
          type: string
          description: Stable hash of the canonical send payload.
        from_header:
          type: string
          description: |
            Raw `From:` header as sent on the wire, including any
            display name (e.g. `"Acme Support" <agent@acme.test>`).
        from_address:
          type: string
          description: Bare email address parsed from `from_header`.
        to_header:
          type: string
          description: |
            Raw `To:` header as sent on the wire, including any
            display name.
        to_address:
          type: string
          description: Bare email address parsed from `to_header`.
        subject:
          type: string
        body_size_bytes:
          type: integer
          description: |
            Total UTF-8 byte length of `body_text` + `body_html`.
            Surfaced on the list endpoint so callers can see "this
            row has a 4MB body" without fetching it.
        content_discarded_at:
          type:
            - string
            - "null"
          format: date-time
          description: |
            Timestamp at which the bodies were discarded by an
            entitlement-driven retention policy. Null when bodies
            are still present. The detail endpoint returns
            null-valued `body_text`/`body_html` for discarded rows.
        message_id:
          type:
            - string
            - "null"
          description: |
            Wire-level Message-ID assigned to the outbound message
            (RFC 5322). Null on rows that never reached signing
            (queued, gate_denied, agent_failed before signing).
        in_reply_to:
          type:
            - string
            - "null"
          description: |
            Wire-level In-Reply-To header value, when this send
            was a reply.
        email_references:
          type:
            - string
            - "null"
          description: |
            Wire-level References header value, when this send
            was a reply.
        in_reply_to_email_id:
          type:
            - string
            - "null"
          format: uuid
          description: |
            Reference to the inbound `emails.id` that this send
            replied to, when known. Populated when the caller used
            /emails/{id}/reply or when /send-mail's `in_reply_to`
            matched a stored inbound message_id in the same org.
        thread_id:
          type:
            - string
            - "null"
          format: uuid
          description: |
            Conversation thread this send belongs to. A reply inherits
            the thread of the inbound it answers; a fresh send starts a
            new thread. Fetch `/threads/{thread_id}` for the full
            ordered thread (inbound + outbound interleaved). NULL on
            gate-denied sends and on sends created before threading was
            enabled.
        queue_id:
          type:
            - string
            - "null"
          description: |
            Message identifier assigned by Primitive's outbound
            relay once the agent accepts the message. Null on
            queued, gate_denied, and agent_failed rows.
        smtp_response_code:
          type:
            - integer
            - "null"
          description: |
            Receiver's 3-digit SMTP code (e.g. 250, 550, 451).
            Populated on terminal delivery statuses; may be null
            on a deferred where the agent never got an SMTP-level
            response (TCP refused, DNS failed, TLS handshake
            failed). `smtp_response_text` still carries Postfix's
            descriptive text in those cases.
        smtp_response_text:
          type:
            - string
            - "null"
          description: |
            Free-form text portion of the receiver's SMTP
            response. The most useful debugging signal on a
            `bounced` or `deferred` row.
        smtp_enhanced_status_code:
          type:
            - string
            - "null"
          description: |
            RFC 3463 enhanced status code (e.g. `5.1.1` for "Bad
            destination mailbox address"). Distinct from
            `smtp_response_code`: the basic 3-digit code is coarse
            (550 = "permanent failure"), the enhanced code is
            finer-grained.
        dkim_selector:
          type:
            - string
            - "null"
          description: |
            DKIM selector used to sign the outbound message.
            Public DNS data; useful for diagnosing why a downstream
            verifier rejected the signature.
        dkim_domain:
          type:
            - string
            - "null"
          description: DKIM signing domain.
        error_code:
          type:
            - string
            - "null"
          description: |
            Stable public error code on `agent_failed` rows. The
            agent's internal codes are remapped to a stable public
            taxonomy (see `publicAgentError` in the server) so this
            field is safe to branch on across agent versions.
        error_message:
          type:
            - string
            - "null"
          description: Free-form error message accompanying `error_code`.
        gates:
          type:
            - array
            - "null"
          items:
            $ref: "#/components/schemas/GateDenial"
          description: |
            Gate-denial detail on `gate_denied` rows. Mirrors the
            synchronous /send-mail 403 contract so a caller's
            GateDenial handler is the same across live denies and
            historical lookups. Null on every other status.
        request_id:
          type:
            - string
            - "null"
          description: |
            Server-issued request identifier from the original
            /send-mail call. Surfaced as the `X-Request-Id`
            response header on the live send and recorded here
            for support escalation.
      required:
        - id
        - status
        - status_changed_at
        - created_at
        - updated_at
        - content_hash
        - from_header
        - from_address
        - to_header
        - to_address
        - subject
        - body_size_bytes
    SemanticSearchField:
      type: string
      enum:
        - subject
        - headers
        - addresses
        - body
      description: A searchable email field.
    SemanticSearchInput:
      type: object
      properties:
        query:
          type: string
          minLength: 1
          maxLength: 2048
          description: |
            Free-text query. Required for `semantic` and `hybrid` modes;
            optional for `keyword` mode.
        mode:
          type: string
          enum:
            - hybrid
            - semantic
            - keyword
          default: hybrid
          description: |
            Ranking strategy. `keyword` is lexical only, `semantic` is
            embedding-based, `hybrid` blends both.
        corpus:
          type: array
          items:
            type: string
            enum:
              - inbound
              - outbound
          minItems: 1
          maxItems: 2
          description: |
            Which mail to search. Defaults to both received (`inbound`)
            and sent (`outbound`).
        search_in:
          type: array
          items:
            $ref: "#/components/schemas/SemanticSearchField"
          description: Restrict matching to these fields. Defaults to all.
        exclude:
          type: array
          items:
            $ref: "#/components/schemas/SemanticSearchField"
          description: Exclude these fields from matching.
        date_from:
          type: string
          format: date-time
          description: Only include mail at or after this timestamp.
        date_to:
          type: string
          format: date-time
          description: Only include mail at or before this timestamp.
        include:
          type: array
          items:
            type: string
            enum:
              - coverage
          description: |
            Opt-in extras. `coverage` adds an index-coverage snapshot to
            `meta`. Matched fields, snippets, and the score breakdown are
            always returned regardless of this field.
        limit:
          type: integer
          minimum: 1
          maximum: 100
          default: 10
          description: Maximum number of results to return.
        cursor:
          type: string
          description: Opaque pagination cursor from a prior response's `meta.cursor`.
    SemanticSearchSnippet:
      type: object
      properties:
        field:
          type: string
          description: The field this excerpt came from.
        text:
          type: string
          description: Plain-text excerpt centered on the match (no markup).
      required:
        - field
        - text
    SemanticSearchScoreBreakdown:
      type: object
      description: |
        Additive contributions to `score`. `semantic` and `keyword` are the
        raw signals times the mode's weight (null when not applicable);
        these plus `field_boost` and `recency` sum to `score` before each
        value is independently rounded to 5 decimal places.
      properties:
        semantic:
          type:
            - number
            - "null"
        keyword:
          type:
            - number
            - "null"
        field_boost:
          type: number
        recency:
          type: number
      required:
        - semantic
        - keyword
        - field_boost
        - recency
    SemanticSearchResult:
      type: object
      properties:
        source_type:
          type: string
          enum:
            - inbound_email
            - sent_email
          description: Whether this row is a received or sent message.
        id:
          type: string
          description: Message id. Combine with `api_url` to fetch the full record.
        subject:
          type:
            - string
            - "null"
        from:
          type:
            - string
            - "null"
        to:
          type:
            - string
            - "null"
        timestamp:
          type: string
          description: Message timestamp (received_at for inbound, created_at for sent).
        status:
          type: string
          description: Lifecycle status of the message.
        score:
          type: number
          description: Overall relevance score; the `score_breakdown` components account for it.
        semantic_score:
          type:
            - number
            - "null"
          description: Raw semantic similarity signal, or null when not applicable.
        keyword_score:
          type:
            - number
            - "null"
          description: Raw keyword (lexical) signal, or null when not applicable.
        matched_fields:
          type: array
          items:
            $ref: "#/components/schemas/SemanticSearchField"
          description: Fields where the query matched.
        snippets:
          type: array
          items:
            $ref: "#/components/schemas/SemanticSearchSnippet"
          description: Match-centered excerpts, one per matched field.
        score_breakdown:
          $ref: "#/components/schemas/SemanticSearchScoreBreakdown"
        api_url:
          type:
            - string
            - "null"
          description: Relative API path to fetch the full message.
      required:
        - source_type
        - id
        - subject
        - from
        - to
        - timestamp
        - status
        - score
        - semantic_score
        - keyword_score
        - matched_fields
        - snippets
        - score_breakdown
        - api_url
    SemanticSearchCoverage:
      type: object
      description: Index-coverage snapshot for the org, returned only when the `coverage` include option is requested.
      properties:
        embedded_chunks:
          type: integer
        pending_chunks:
          type: integer
        skipped_plan_chunks:
          type: integer
        skipped_quota_chunks:
          type: integer
        unsupported_attachment_chunks:
          type: integer
        failed_chunks:
          type: integer
      required:
        - embedded_chunks
        - pending_chunks
        - skipped_plan_chunks
        - skipped_quota_chunks
        - unsupported_attachment_chunks
        - failed_chunks
    SemanticSearchMeta:
      type: object
      properties:
        limit:
          type: integer
          description: Page size used for this request.
        cursor:
          type:
            - string
            - "null"
          description: Cursor for the next page, or null if there are no more results.
        mode:
          type: string
          enum:
            - hybrid
            - semantic
            - keyword
          description: Ranking mode used for this response.
        coverage:
          oneOf:
            - $ref: "#/components/schemas/SemanticSearchCoverage"
            - type: "null"
          description: |
            Index-coverage snapshot, present only when requested via
            `include: [coverage]`; otherwise null.
      required:
        - limit
        - cursor
        - mode
        - coverage
    SentEmailDetail:
      description: |
        Full sent-email record, including `body_text` and
        `body_html`. Returned by /sent-emails/{id}.
      allOf:
        - $ref: "#/components/schemas/SentEmailSummary"
        - type: object
          properties:
            body_text:
              type:
                - string
                - "null"
              description: |
                Plain-text body sent on the wire. Null when the
                send carried only an HTML body, or when bodies have
                been discarded post-send (`content_discarded_at`
                set).
            body_html:
              type:
                - string
                - "null"
              description: |
                HTML body sent on the wire. Null when the send
                carried only a plain-text body, or when bodies
                have been discarded post-send.
    ReplyInput:
      type: object
      additionalProperties: false
      description: |
        Body shape for `/emails/{id}/reply`. Intentionally narrow:
        recipients (`to`), subject, and threading headers
        (`in_reply_to`, `references`) are derived server-side from
        the inbound row referenced by the path id and are rejected by
        `additionalProperties` if passed (returns 400).

        `from` IS allowed because of legitimate use cases (display-name
        addition, replying from a different verified outbound address,
        multi-team triage). Send-mail's per-send `canSendFrom` gate
        validates the from-domain regardless, so the override carries
        no extra privilege.
      properties:
        body_text:
          type: string
          description: Plain-text reply body. At least one of body_text or body_html is required. The combined UTF-8 byte length of body_text and body_html must be at most 262144 bytes (same cap as send-mail).
        body_html:
          type: string
          description: HTML reply body. At least one of body_text or body_html is required.
        from:
          type: string
          minLength: 3
          maxLength: 998
          description: |
            Optional override for the reply's From header. Defaults to
            the inbound's recipient. Use to add a display name (`"Acme
            Support" <agent@company.com>`) or to reply from a different
            verified outbound address (e.g. multi-team routing where
            support@ triages to billing@). The from-domain must be a
            verified outbound domain for your org, same as send-mail.
        wait:
          type: boolean
          description: When true, wait for the first downstream SMTP delivery outcome before returning, mirroring the send-mail `wait` semantics.
        attachments:
          type: array
          maxItems: 100
          description: Inline attachments for this reply. Use https://api.primitive.dev/v1 for replies with attachments. Combined raw decoded attachment bytes must be at most 31457280.
          items:
            $ref: "#/components/schemas/SendMailAttachment"
    SendMailResult:
      type: object
      properties:
        id:
          type: string
          description: Persisted sent-email attempt ID.
        status:
          $ref: "#/components/schemas/SentEmailStatus"
        from:
          type: string
          description: |
            Bare from-address actually written on the wire. Echoed
            on every success branch so callers can confirm what
            went out, particularly useful for the /emails/{id}/reply
            path where `from` is server-derived from the inbound's
            recipient when the caller doesn't override.

            For sends where the caller passed a from-header that
            included a display name (e.g. `"Acme Support" <support@acme.test>`),
            this field is the parsed bare address (`support@acme.test`).
            The display name was sent on the wire intact; this field
            just makes the address easy to compare against allowlists.
        queue_id:
          type:
            - string
            - "null"
          description: |
            Message identifier assigned by Primitive's OUTBOUND relay
            (the box that signs your mail and submits it to the
            receiving MTA). NOT the receiver's queue id.

            The receiver may also report its own queue id in
            `smtp_response_text` (e.g. `"250 2.0.0 Ok: queued as
            99D111927CDA"` from a Postfix receiver). Those two ids
            refer to different mail systems and are NOT comparable.
            Treat `queue_id` as Primitive-internal and the
            receiver's id as remote-system-internal.

            Null on rows that never reached the relay (queued,
            gate_denied, agent_failed before signing).
        accepted:
          type: array
          items:
            type: string
          description: Recipient addresses accepted by the relay.
        rejected:
          type: array
          items:
            type: string
          description: Recipient addresses rejected by the relay.
        client_idempotency_key:
          type: string
          description: Effective idempotency key used for this send.
        request_id:
          type: string
          description: Server-issued request identifier for support and tracing.
        content_hash:
          type: string
          description: Stable hash of the canonical send payload.
        delivery_status:
          $ref: "#/components/schemas/DeliveryStatus"
        smtp_response_code:
          type:
            - integer
            - "null"
          description: SMTP response code from the first downstream delivery outcome when wait is true.
        smtp_response_text:
          type: string
          description: SMTP response text from the first downstream delivery outcome when wait is true.
        idempotent_replay:
          type: boolean
          description: |
            True when the response replays a previously-recorded send
            keyed by `client_idempotency_key` (same key, same canonical
            payload). False on a fresh send and on gate-denied
            responses. Lets callers branch on cache state without
            diffing fields.
      required:
        - id
        - status
        - from
        - queue_id
        - accepted
        - rejected
        - client_idempotency_key
        - request_id
        - content_hash
        - idempotent_replay
    SendPermissionRule:
      description: |
        One recipient-scope rule describing a destination the caller
        may send to. Discriminated on `type`. Each rule carries a
        human-prose `description` field intended for display.

        Rule kinds are stable within an SDK release. A response
        containing a `type` value not enumerated in this schema
        means the server is running a newer version than the SDK;
        upgrade the SDK to the release that matches the server's
        schema. Strict-parsing SDKs (Go, Python) will raise a
        decode error in that case rather than silently dropping
        the unknown rule, since silent drops would let an outbound
        agent reason from an incomplete view of its own permissions.
      discriminator:
        propertyName: type
        mapping:
          any_recipient: "#/components/schemas/SendPermissionAnyRecipient"
          managed_zone: "#/components/schemas/SendPermissionManagedZone"
          your_domain: "#/components/schemas/SendPermissionYourDomain"
          address: "#/components/schemas/SendPermissionAddress"
      oneOf:
        - $ref: "#/components/schemas/SendPermissionAnyRecipient"
        - $ref: "#/components/schemas/SendPermissionManagedZone"
        - $ref: "#/components/schemas/SendPermissionYourDomain"
        - $ref: "#/components/schemas/SendPermissionAddress"
    SendPermissionAnyRecipient:
      type: object
      description: |
        The caller can send to any recipient. When this rule is
        present, every other rule in the response is redundant.
      properties:
        type:
          type: string
          enum:
            - any_recipient
        description:
          type: string
          description: Human-prose summary of the rule.
      required:
        - type
        - description
    SendPermissionManagedZone:
      type: object
      description: |
        The caller can send to any address at the named
        Primitive-managed zone. Always emitted (no entitlement
        required) because Primitive owns the zone and every mailbox
        belongs to a Primitive customer by construction.
      properties:
        type:
          type: string
          enum:
            - managed_zone
        zone:
          type: string
          description: |
            The managed apex domain. Sends are accepted to any
            address at the apex itself or any subdomain (e.g.
            `alice@primitive.email` and `alice@acme.primitive.email`
            both match the `primitive.email` zone rule).
        description:
          type: string
          description: Human-prose summary of the rule.
      required:
        - type
        - zone
        - description
    SendPermissionYourDomain:
      type: object
      description: |
        The caller can send to any address at one of their own
        verified outbound domains. Emitted once per active row in
        the org's `domains` table.
      properties:
        type:
          type: string
          enum:
            - your_domain
        domain:
          type: string
          description: A verified outbound domain owned by the caller's org.
        description:
          type: string
          description: Human-prose summary of the rule.
      required:
        - type
        - domain
        - description
    SendPermissionAddress:
      type: object
      description: |
        The caller can send to a specific address that has
        authenticated inbound mail to the org. Emitted once per row
        in the org's `known_send_addresses` table, capped at
        `meta.address_cap`.
      properties:
        type:
          type: string
          enum:
            - address
        address:
          type: string
          description: The bare email address this rule grants sends to.
        last_received_at:
          type: string
          format: date-time
          description: |
            Most recent inbound email from this address that
            authenticated successfully (DMARC pass + DKIM/SPF
            alignment). Updated on each new authenticated receipt.
        received_count:
          type: integer
          description: |
            Total number of authenticated inbound emails from this
            address. Increments only when `last_received_at` advances.
        description:
          type: string
          description: Human-prose summary of the rule.
      required:
        - type
        - address
        - last_received_at
        - received_count
        - description
    SendPermissionsMeta:
      type: object
      description: |
        Response metadata for /send-permissions. The `address_cap`
        bounds the size of the `address` rule subset; orgs with more
        than `address_cap` known addresses almost always also hold a
        broader rule type (`any_recipient` or `your_domain`), so the
        cap is a response-size bound rather than a meaningful
        product limit.
      properties:
        address_cap:
          type: integer
          description: Maximum number of `address` rules included in `data`.
        truncated:
          type: boolean
          description: |
            True when the org has more than `address_cap` known
            addresses and the list was truncated. False when every
            known address is represented or when the org holds no
            address rules at all.
      required:
        - address_cap
        - truncated
    Endpoint:
      type: object
      properties:
        id:
          type: string
          format: uuid
        org_id:
          type: string
          format: uuid
        url:
          type:
            - string
            - "null"
        enabled:
          type: boolean
        domain_id:
          type:
            - string
            - "null"
          format: uuid
          description: Restrict this endpoint to emails from a specific domain
        rules:
          type: object
          description: Endpoint-specific filtering rules
        created_at:
          type: string
          format: date-time
        updated_at:
          type: string
          format: date-time
        delivery_count:
          type: integer
          description: Total webhook deliveries attempted
        success_count:
          type: integer
          description: Successful deliveries
        failure_count:
          type: integer
          description: Failed deliveries
        consecutive_fails:
          type: integer
          description: Current streak of consecutive failures
        last_delivery_at:
          type:
            - string
            - "null"
          format: date-time
        last_success_at:
          type:
            - string
            - "null"
          format: date-time
        last_failure_at:
          type:
            - string
            - "null"
          format: date-time
        deactivated_at:
          type:
            - string
            - "null"
          format: date-time
        kind:
          type: string
          enum:
            - http
            - function
          description: "http: deliver to the webhook URL. function: invoke a Primitive Function."
        function_id:
          type:
            - string
            - "null"
          format: uuid
          description: The Function this endpoint invokes, when kind is function.
        is_route_target:
          type: boolean
          description: |
            When true, this endpoint is reachable only via an explicit recipient
            route, never as a domain's default destination, and is exempt from
            the one-endpoint-per-domain rule (so many can share a domain).
      required:
        - id
        - org_id
        - enabled
        - rules
        - created_at
        - updated_at
        - delivery_count
        - success_count
        - failure_count
        - consecutive_fails
    CreateEndpointInput:
      type: object
      additionalProperties: false
      properties:
        kind:
          type: string
          enum:
            - http
            - function
          default: http
          description: "http: deliver to a webhook URL (provide url). function: invoke a Primitive Function (provide function_id, omit url)."
        url:
          type: string
          minLength: 1
          description: The webhook URL to deliver events to. Required when kind is http; omit for function endpoints.
        function_id:
          type: string
          format: uuid
          description: The Function to invoke. Required when kind is function.
        enabled:
          type: boolean
          default: true
          description: Whether the endpoint is active
        domain_id:
          type:
            - string
            - "null"
          format: uuid
          description: Restrict to emails from a specific domain
        rules:
          type: object
          description: Endpoint-specific filtering rules
        is_route_target:
          type: boolean
          default: false
          description: |
            Create this endpoint as a route-target: reachable only via an
            explicit recipient route, never a domain's default destination, and
            exempt from the one-endpoint-per-domain rule.
    UpdateEndpointInput:
      type: object
      additionalProperties: false
      properties:
        url:
          type: string
          minLength: 1
          description: New webhook URL (triggers endpoint rotation)
        enabled:
          type: boolean
        domain_id:
          type:
            - string
            - "null"
          format: uuid
        rules:
          type: object
      minProperties: 1
    TestResult:
      type: object
      properties:
        status:
          type: integer
          description: HTTP status code returned by the endpoint
        body:
          type: string
          description: Response body (truncated to 1000 characters)
        signature:
          type: string
          description: The signature header value sent (if webhook secret is configured)
      required:
        - status
        - body
    Filter:
      type: object
      properties:
        id:
          type: string
          format: uuid
        org_id:
          type: string
          format: uuid
        domain_id:
          type:
            - string
            - "null"
          format: uuid
          description: If set, filter applies only to this domain
        type:
          type: string
          enum:
            - whitelist
            - blocklist
        pattern:
          type: string
          description: Email address or pattern to match (stored lowercase)
        enabled:
          type: boolean
        created_at:
          type: string
          format: date-time
      required:
        - id
        - org_id
        - type
        - pattern
        - enabled
        - created_at
    CreateFilterInput:
      type: object
      additionalProperties: false
      properties:
        type:
          type: string
          enum:
            - whitelist
            - blocklist
        pattern:
          type: string
          minLength: 1
          maxLength: 500
          description: Email address or pattern to filter
        domain_id:
          type:
            - string
            - "null"
          format: uuid
          description: Restrict filter to a specific domain (Pro plan required)
      required:
        - type
        - pattern
    UpdateFilterInput:
      type: object
      additionalProperties: false
      properties:
        enabled:
          type: boolean
      required:
        - enabled
    WakeSchedule:
      type: object
      description: A cron schedule that sends a wake.dispatch command to a function.
      properties:
        id:
          type: string
          format: uuid
        from_address:
          type:
            - string
            - "null"
          description: The sending identity the wake is signed as.
        target_address:
          type: string
          description: The function address the wake is delivered to.
        command:
          type: string
        args:
          type: object
          additionalProperties: true
        cron_expr:
          type: string
          description: 5-field cron expression.
        timezone:
          type: string
          description: IANA timezone the cron is evaluated in.
        next_run_at:
          type: string
          format: date-time
        last_run_at:
          type:
            - string
            - "null"
          format: date-time
        enabled:
          type: boolean
        note:
          type:
            - string
            - "null"
        created_at:
          type: string
          format: date-time
        updated_at:
          type: string
          format: date-time
      required:
        - id
        - target_address
        - command
        - cron_expr
        - timezone
        - next_run_at
        - enabled
        - created_at
        - updated_at
    CreateWakeScheduleInput:
      type: object
      additionalProperties: false
      properties:
        from_address:
          type: string
          description: Sending identity (must be a domain the org can sign).
        target_address:
          type: string
          description: Your function address (must differ from from_address).
        command:
          type: string
          minLength: 1
          maxLength: 200
        args:
          type: object
          additionalProperties: true
          description: Optional JSON object passed through to the woken function.
        cron_expr:
          type: string
          minLength: 1
          maxLength: 120
        timezone:
          type: string
          minLength: 1
          maxLength: 64
          default: UTC
        note:
          type: string
          maxLength: 2000
      required:
        - from_address
        - target_address
        - command
        - cron_expr
    UpdateWakeScheduleInput:
      type: object
      additionalProperties: false
      properties:
        enabled:
          type: boolean
        command:
          type: string
          minLength: 1
          maxLength: 200
        args:
          type: object
          additionalProperties: true
        cron_expr:
          type: string
          minLength: 1
          maxLength: 120
        timezone:
          type: string
          minLength: 1
          maxLength: 64
        from_address:
          type: string
        target_address:
          type: string
        note:
          type:
            - string
            - "null"
          maxLength: 2000
    WakeAuthorization:
      type: object
      description: A per-target allowlist grant authorizing a sender to wake a function.
      properties:
        id:
          type: string
          format: uuid
        recipient_endpoint_id:
          type: string
          format: uuid
        allowed_sender_domain:
          type: string
        allowed_sender_address:
          type:
            - string
            - "null"
        allowed_commands:
          type:
            - array
            - "null"
          items:
            type: string
        enabled:
          type: boolean
        note:
          type:
            - string
            - "null"
        created_at:
          type: string
          format: date-time
      required:
        - id
        - recipient_endpoint_id
        - allowed_sender_domain
        - enabled
        - created_at
    CreateWakeAuthorizationInput:
      type: object
      additionalProperties: false
      properties:
        recipient_endpoint_id:
          type: string
          format: uuid
        allowed_sender_domain:
          type: string
          minLength: 1
          maxLength: 253
          description: Fully-qualified sender domain (at least two labels).
        allowed_sender_address:
          type:
            - string
            - "null"
          description: Optional specific sender address to pin the grant to.
        allowed_commands:
          type:
            - array
            - "null"
          maxItems: 64
          items:
            type: string
            minLength: 1
            maxLength: 200
          description: Optional command allowlist; null = any command.
        note:
          type: string
          maxLength: 2000
      required:
        - recipient_endpoint_id
        - allowed_sender_domain
    UpdateWakeAuthorizationInput:
      type: object
      additionalProperties: false
      properties:
        enabled:
          type: boolean
      required:
        - enabled
    WakeDispatch:
      type: object
      description: A recorded wake.dispatch interaction (audit row).
      properties:
        id:
          type: string
          format: uuid
        wire_id:
          type: string
        role:
          type: string
        state:
          type: string
        outcome:
          type:
            - string
            - "null"
        awaiting:
          type:
            - string
            - "null"
        counterparty_address:
          type: string
        our_address:
          type: string
        step_count:
          type: integer
        created_at:
          type: string
          format: date-time
        completed_at:
          type:
            - string
            - "null"
          format: date-time
      required:
        - id
        - wire_id
        - role
        - state
        - counterparty_address
        - our_address
        - created_at
    RecipientRoute:
      type: object
      description: A recipient routing rule binding an address pattern to one endpoint.
      properties:
        id:
          type: string
          format: uuid
        org_id:
          type: string
          format: uuid
        domain_id:
          type:
            - string
            - "null"
          format: uuid
          description: Domain the route is scoped to; null = org-wide.
        match_type:
          type: string
          enum:
            - exact
            - wildcard
            - regex
        pattern:
          type: string
          description: The recipient address pattern (an exact address or a wildcard).
        pattern_norm:
          type:
            - string
            - "null"
          description: Normalized pattern used for matching.
        endpoint_id:
          type: string
          format: uuid
          description: The endpoint inbound mail matching this rule is delivered to.
        priority:
          type: integer
          description: Evaluation order within a scope; lower is checked first.
        enabled:
          type: boolean
        match_count:
          type: string
          description: How many emails have matched this rule (a bigint, returned as a string).
        last_matched_at:
          type:
            - string
            - "null"
          format: date-time
        created_at:
          type: string
          format: date-time
      required:
        - id
    CreateRouteInput:
      type: object
      additionalProperties: false
      description: |
        Provide exactly one of `endpoint_id` or `function_id`. With `function_id`,
        a route-target endpoint is minted for that function and the route is bound
        to it in one transaction.
      properties:
        match_type:
          type: string
          enum:
            - exact
            - wildcard
            - regex
        pattern:
          type: string
          minLength: 1
          maxLength: 512
        endpoint_id:
          type: string
          format: uuid
          description: An existing endpoint to route to. Mutually exclusive with function_id.
        function_id:
          type: string
          format: uuid
          description: Route to this function, minting its route-target endpoint if needed. Mutually exclusive with endpoint_id.
        domain_id:
          type:
            - string
            - "null"
          format: uuid
          description: Scope the route to a domain; defaults to the pattern's domain.
        priority:
          type: integer
          minimum: 0
          maximum: 1000000
        enabled:
          type: boolean
      required:
        - match_type
        - pattern
    UpdateRouteInput:
      type: object
      additionalProperties: false
      properties:
        match_type:
          type: string
          enum:
            - exact
            - wildcard
            - regex
        pattern:
          type: string
          minLength: 1
          maxLength: 512
        endpoint_id:
          type: string
          format: uuid
        domain_id:
          type:
            - string
            - "null"
          format: uuid
        priority:
          type: integer
          minimum: 0
          maximum: 1000000
        enabled:
          type: boolean
    ReorderRoutesInput:
      type: object
      additionalProperties: false
      properties:
        updates:
          type: array
          minItems: 1
          maxItems: 1000
          items:
            type: object
            additionalProperties: false
            properties:
              id:
                type: string
                format: uuid
              priority:
                type: integer
                minimum: 0
                maximum: 1000000
            required:
              - id
              - priority
      required:
        - updates
    SimulateRouteInput:
      type: object
      additionalProperties: false
      properties:
        recipient:
          type: string
          minLength: 1
          maxLength: 320
        event_type:
          type: string
          minLength: 1
          maxLength: 100
          description: Event type to model; defaults to email.received.
      required:
        - recipient
    RouteEvaluatedEntry:
      type: object
      properties:
        route_id:
          type: string
        tier:
          type: string
          enum:
            - exact
            - wildcard
            - regex
        pattern:
          type: string
        result:
          type: string
          enum:
            - hit
            - miss
            - skipped
            - error
        reason:
          type: string
      required:
        - route_id
        - tier
        - pattern
        - result
    SimulateRouteResult:
      type: object
      description: Where an inbound email to the recipient would be delivered, and why.
      properties:
        outcome:
          type: string
          enum:
            - matched
            - defaulted
            - none
        recipient:
          type: string
        endpoint_id:
          type:
            - string
            - "null"
        matched_route_id:
          type:
            - string
            - "null"
        matched_tier:
          type:
            - string
            - "null"
          enum:
            - exact
            - wildcard
            - regex
            - null
        matched_pattern:
          type:
            - string
            - "null"
        default_scope:
          type:
            - string
            - "null"
          enum:
            - domain
            - org
            - null
        evaluated:
          type: array
          items:
            $ref: "#/components/schemas/RouteEvaluatedEntry"
        truncated:
          type: boolean
      required:
        - outcome
        - recipient
        - endpoint_id
        - matched_route_id
        - matched_tier
        - matched_pattern
        - default_scope
        - evaluated
        - truncated
    DeliverySummary:
      type: object
      properties:
        id:
          type: string
          description: Delivery ID (numeric string)
        email_id:
          type: string
          format: uuid
        org_id:
          type: string
          format: uuid
        endpoint_id:
          type: string
          format: uuid
        endpoint_url:
          type: string
        status:
          type: string
          enum:
            - pending
            - delivered
            - header_confirmed
            - failed
        attempt_count:
          type: integer
        duration_ms:
          type:
            - integer
            - "null"
        last_error:
          type:
            - string
            - "null"
        created_at:
          type: string
          format: date-time
        updated_at:
          type: string
          format: date-time
        email:
          type:
            - object
            - "null"
          properties:
            sender:
              type: string
            recipient:
              type: string
            subject:
              type:
                - string
                - "null"
          required:
            - sender
            - recipient
      required:
        - id
        - email_id
        - org_id
        - endpoint_id
        - endpoint_url
        - status
        - attempt_count
        - created_at
        - updated_at
    ReplayResult:
      type: object
      properties:
        delivered:
          type: integer
          description: Number of successful deliveries
        failed:
          type: integer
          description: Number of failed deliveries
      required:
        - delivered
        - failed
    DiscardContentResult:
      type: object
      properties:
        discarded:
          type: boolean
          description: |
            Always `true` on a 2xx response. The content is either now
            discarded as a result of this call, or was already discarded
            before this call ran.
        already_discarded:
          type: boolean
          description: |
            `true` if the email's content was already discarded before
            this call ran (no work was done). `false` if this call was
            the one that performed the discard.
      required:
        - discarded
        - already_discarded
    FunctionDeployStatus:
      type: string
      enum:
        - pending
        - deployed
        - failed
      description: |
        Lifecycle state of the latest deploy attempt:
          * `pending` — deploy in flight; the runtime has not yet
            confirmed the new bundle is live.
          * `deployed` — the running edge handler is the latest code.
          * `failed` — the most recent deploy attempt failed; the
            previously-live code (if any) is still running. The
            `deploy_error` field carries the error message.
    FunctionListItem:
      type: object
      description: One row from the function listing.
      properties:
        id:
          type: string
          format: uuid
          description: Function id, also the script name in the edge runtime.
        name:
          type: string
          description: Slug-style name set on creation. Stable; cannot be changed.
        deploy_status:
          $ref: "#/components/schemas/FunctionDeployStatus"
        deployed_at:
          type:
            - string
            - "null"
          format: date-time
          description: Timestamp of the most recent successful deploy. Null until the first deploy succeeds.
        created_at:
          type: string
          format: date-time
        updated_at:
          type: string
          format: date-time
      required:
        - id
        - name
        - deploy_status
        - created_at
        - updated_at
    FunctionDetail:
      type: object
      description: Full function record returned by GET / PUT.
      properties:
        id:
          type: string
          format: uuid
        name:
          type: string
        code:
          type: string
          description: |
            The bundled handler source. UTF-8 string up to 1 MiB. The
            same value most recently passed as `code` to POST or PUT.
        deploy_status:
          $ref: "#/components/schemas/FunctionDeployStatus"
        deploy_error:
          type:
            - string
            - "null"
          description: |
            Error message from the most recent failed deploy, or null
            after a successful deploy. Surface this to users to explain
            a `failed` status without polling.
        deployed_at:
          type:
            - string
            - "null"
          format: date-time
        created_at:
          type: string
          format: date-time
        updated_at:
          type: string
          format: date-time
      required:
        - id
        - name
        - code
        - deploy_status
        - created_at
        - updated_at
    CreateFunctionInput:
      type: object
      additionalProperties: false
      properties:
        name:
          type: string
          pattern: ^[a-z0-9_-]{1,64}$
          description: |
            Slug-style name. Lowercase letters, digits, hyphens, and
            underscores. 1 to 64 characters. Must be unique within the
            org; a 409 is returned on collision.
        code:
          type: string
          minLength: 1
          maxLength: 1048576
          description: |
            Pre-built handler as a single ESM module. Up to 1 MiB UTF-8.
            Must export a default `{ async fetch(req, env, ctx) { ... } }`
            object. Provide either `code` or `files`, not both.
        sourceMap:
          type: string
          minLength: 1
          maxLength: 5242880
          description: |
            Optional source map for the bundle. Up to 5 MiB UTF-8.
            Stored with the deployment attempt and sent to the runtime
            to symbolicate stack traces in the function's logs. Only
            valid with `code`.
        files:
          type: object
          additionalProperties:
            type: string
          description: |
            Source files for a managed build, as a map of path to file
            contents (for example {"package.json": "...",
            "src/index.ts": "..."}). Provide this INSTEAD of `code` to
            have the server install dependencies and bundle the source
            for the Workers runtime before deploying. Include a
            package.json (its `dependencies` are installed). Provide
            either `code` or `files`, not both.
      required:
        - name
    CreateFunctionResult:
      type: object
      description: Returned by POST /functions on a successful deploy.
      properties:
        id:
          type: string
          format: uuid
        name:
          type: string
        deploy_status:
          $ref: "#/components/schemas/FunctionDeployStatus"
      required:
        - id
        - name
        - deploy_status
    UpdateFunctionInput:
      type: object
      additionalProperties: false
      properties:
        code:
          type: string
          minLength: 1
          maxLength: 1048576
          description: New pre-built handler. Same rules as CreateFunctionInput.code. Provide either `code` or `files`, not both.
        sourceMap:
          type: string
          minLength: 1
          maxLength: 5242880
        files:
          type: object
          additionalProperties:
            type: string
          description: |
            Source files for a managed build, as a map of path to file
            contents. Provide this INSTEAD of `code` to rebuild and
            redeploy from source. Same rules as CreateFunctionInput.files.
        managedRelease:
          type: object
          additionalProperties: false
          description: Strict exact-redeploy claim for a prepared managed-agent Function. Valid only with `code` and an unscoped organization API key. The uploaded code must match the prepared release runtime bundle exactly.
          properties:
            releaseId:
              type: string
              format: uuid
              description: Prepared managed-agent release UUID.
            expectedManifestHash:
              type: string
              pattern: ^[a-f0-9]{64}$
              description: Exact lowercase SHA-256 manifest hash returned by release preparation.
          required:
            - releaseId
            - expectedManifestHash
      required: []
    TestInvocationResult:
      type: object
      description: |
        Metadata returned by POST /functions/{id}/test. The send is
        queued; poll `trace_url` to watch the run progress through
        send -> inbound -> webhook deliveries -> outbound requests,
        logs, and replies.
      properties:
        test_run_id:
          type: string
          format: uuid
          description: Durable test run id used to fetch the run trace.
        inbound_domain:
          type: string
          description: Verified inbound domain the test email was sent to.
        to:
          type: string
          description: Synthetic local-part plus inbound_domain. Visible in the org's inbox.
        from:
          type: string
          description: Primitive-controlled outbound sender used for the test.
        send_id:
          type: string
          description: |
            Outbound message id from the underlying send. NOT the
            inbound email's id; the inbound id is created when the
            email arrives via MX and lands on the function's
            invocations list.
        subject:
          type: string
          description: Subject placed on the test email so it can be located in the inbox.
        poll_since:
          type: string
          format: date-time
          description: |
            ISO timestamp suitable as a `since` lower bound when
            polling /emails for the inbound's arrival. Captured
            slightly before the send to absorb light clock skew.
        watch_url:
          type: string
          format: uri
          description: Function detail page where invocations show up live.
        trace_url:
          type: string
          description: Relative API URL for GET /functions/{id}/test-runs/{test_run_id}/trace.
      required:
        - test_run_id
        - inbound_domain
        - to
        - from
        - send_id
        - subject
        - poll_since
        - watch_url
        - trace_url
    FunctionRouting:
      type: object
      description: |
        A single route binding for a function. `domain` is null when the
        binding is the org's fallback (any active domain without a scoped
        binding); otherwise it carries the scoped domain. `rules` is
        reserved for future routing predicates.
      properties:
        endpoint_id:
          type: string
          format: uuid
        enabled:
          type: boolean
        domain:
          type:
            - object
            - "null"
          properties:
            id:
              type: string
              format: uuid
            name:
              type:
                - string
                - "null"
          required:
            - id
        rules:
          type: object
          description: Future routing predicates. Currently empty.
        delivery_count:
          type: integer
        success_count:
          type: integer
        failure_count:
          type: integer
        consecutive_fails:
          type: integer
        last_delivery_at:
          type:
            - string
            - "null"
          format: date-time
        last_success_at:
          type:
            - string
            - "null"
          format: date-time
        last_failure_at:
          type:
            - string
            - "null"
          format: date-time
      required:
        - endpoint_id
        - enabled
        - domain
        - rules
    RoutingTopology:
      type: object
      description: |
        Org-wide map of function routing: which domain points at which
        function, the org's fallback binding (if any), and every
        deployed function with no route currently bound.
      properties:
        domains:
          type: array
          items:
            type: object
            properties:
              domain_id:
                type: string
                format: uuid
              domain:
                type: string
              routed_function:
                type:
                  - object
                  - "null"
                properties:
                  id:
                    type: string
                    format: uuid
                  name:
                    type: string
                required:
                  - id
                  - name
              endpoint_enabled:
                type:
                  - boolean
                  - "null"
            required:
              - domain_id
              - domain
              - routed_function
              - endpoint_enabled
        fallback_function:
          type:
            - object
            - "null"
          properties:
            id:
              type: string
              format: uuid
            name:
              type: string
          required:
            - id
            - name
        fallback_enabled:
          type:
            - boolean
            - "null"
        unrouted_functions:
          type: array
          items:
            type: object
            properties:
              id:
                type: string
                format: uuid
              name:
                type: string
            required:
              - id
              - name
      required:
        - domains
        - fallback_function
        - fallback_enabled
        - unrouted_functions
    FunctionRouteBody:
      type: object
      description: |
        Target for a route binding. Either a specific verified domain
        (scoped) or the org-wide fallback. Pass `takeover: true` to
        deactivate any conflicting binding before installing this one.
      properties:
        target:
          oneOf:
            - type: object
              properties:
                kind:
                  type: string
                  enum:
                    - domain
                domainId:
                  type: string
                  format: uuid
              required:
                - kind
                - domainId
            - type: object
              properties:
                kind:
                  type: string
                  enum:
                    - fallback
              required:
                - kind
        takeover:
          type: boolean
          description: When true, deactivate any conflicting binding before installing this one.
      required:
        - target
    FunctionRouteResult:
      type: object
      description: |
        On success, carries the new `routing`. On conflict, carries
        `conflict` describing the binding holder so the caller can
        re-issue with `takeover: true`.
      properties:
        routing:
          oneOf:
            - $ref: "#/components/schemas/FunctionRouting"
            - type: "null"
        conflict:
          type: object
          properties:
            kind:
              type: string
              enum:
                - http
                - function
            functionId:
              type:
                - string
                - "null"
              format: uuid
            functionName:
              type:
                - string
                - "null"
            url:
              type:
                - string
                - "null"
          required:
            - kind
    FunctionTestRunState:
      type: string
      description: |
        High-level state for a function test run trace:
          - `send_failed`: the initial test email send failed.
          - `waiting_for_send`: the test run was created but no send result has been recorded yet.
          - `waiting_for_inbound`: the test send was queued and the matching inbound email has not arrived yet.
          - `waiting_for_function`: the inbound email arrived and webhook/function processing is still in flight.
          - `completed`: the function webhook completed successfully.
          - `failed`: webhook delivery exhausted retries.
      enum:
        - send_failed
        - waiting_for_send
        - waiting_for_inbound
        - waiting_for_function
        - completed
        - failed
    FunctionTestRun:
      type: object
      properties:
        id:
          type: string
          format: uuid
        function_id:
          type: string
          format: uuid
        inbound_domain:
          type: string
        to:
          type: string
        from:
          type: string
        subject:
          type: string
        poll_since:
          type: string
          format: date-time
        created_at:
          type: string
          format: date-time
        sent_at:
          type:
            - string
            - "null"
          format: date-time
        send_error:
          type:
            - string
            - "null"
      required:
        - id
        - function_id
        - inbound_domain
        - to
        - from
        - subject
        - poll_since
        - created_at
        - sent_at
        - send_error
    FunctionTestRunSend:
      type:
        - object
        - "null"
      properties:
        id:
          type: string
          format: uuid
        status:
          $ref: "#/components/schemas/SentEmailStatus"
        queue_id:
          type:
            - string
            - "null"
        created_at:
          type: string
          format: date-time
        updated_at:
          type: string
          format: date-time
      required:
        - id
        - status
        - queue_id
        - created_at
        - updated_at
    FunctionTestRunInboundEmail:
      type:
        - object
        - "null"
      properties:
        id:
          type: string
          format: uuid
        status:
          $ref: "#/components/schemas/EmailStatus"
        received_at:
          type: string
          format: date-time
        from:
          type: string
        to:
          type: string
        subject:
          type:
            - string
            - "null"
        webhook_status:
          $ref: "#/components/schemas/EmailWebhookStatus"
        webhook_attempt_count:
          type: integer
        webhook_last_status_code:
          type:
            - integer
            - "null"
        webhook_last_error:
          type:
            - string
            - "null"
      required:
        - id
        - status
        - received_at
        - from
        - to
        - subject
        - webhook_status
        - webhook_attempt_count
        - webhook_last_status_code
        - webhook_last_error
    FunctionTestRunDeliveryEndpoint:
      type:
        - object
        - "null"
      properties:
        id:
          type: string
          format: uuid
        kind:
          type: string
          description: Endpoint kind. Current traces may include `http` or `function`; future endpoint kinds may appear.
        function_id:
          type:
            - string
            - "null"
          format: uuid
        function_name:
          type:
            - string
            - "null"
        domain_id:
          type:
            - string
            - "null"
          format: uuid
        enabled:
          type: boolean
        deactivated_at:
          type:
            - string
            - "null"
          format: date-time
        is_current_function:
          type: boolean
      required:
        - id
        - kind
        - function_id
        - function_name
        - domain_id
        - enabled
        - deactivated_at
        - is_current_function
    FunctionTestRunDelivery:
      type: object
      properties:
        id:
          type: string
          description: Webhook delivery id.
        endpoint_id:
          type: string
          format: uuid
        endpoint_url:
          type: string
          format: uri
        status:
          type: string
          enum:
            - pending
            - delivered
            - header_confirmed
            - failed
        attempt_count:
          type: integer
        duration_ms:
          type:
            - integer
            - "null"
        last_error:
          type:
            - string
            - "null"
        last_error_code:
          type:
            - string
            - "null"
        created_at:
          type: string
          format: date-time
        updated_at:
          type: string
          format: date-time
        endpoint:
          $ref: "#/components/schemas/FunctionTestRunDeliveryEndpoint"
      required:
        - id
        - endpoint_id
        - endpoint_url
        - status
        - attempt_count
        - duration_ms
        - last_error
        - last_error_code
        - created_at
        - updated_at
        - endpoint
    FunctionTestRunOutboundRequest:
      type: object
      properties:
        id:
          type: string
          format: uuid
        function_id:
          type: string
          format: uuid
        webhook_delivery_id:
          type:
            - string
            - "null"
        email_id:
          type:
            - string
            - "null"
          format: uuid
        endpoint_id:
          type:
            - string
            - "null"
          format: uuid
        method:
          type: string
        url:
          type: string
          format: uri
        host:
          type: string
        path:
          type: string
        status_code:
          type:
            - integer
            - "null"
        ok:
          type:
            - boolean
            - "null"
        duration_ms:
          type: integer
        error:
          type:
            - string
            - "null"
        ts:
          type: string
          format: date-time
      required:
        - id
        - function_id
        - webhook_delivery_id
        - email_id
        - endpoint_id
        - method
        - url
        - host
        - path
        - status_code
        - ok
        - duration_ms
        - error
        - ts
    FunctionTestRunReply:
      type: object
      properties:
        id:
          type: string
          format: uuid
        status:
          $ref: "#/components/schemas/SentEmailStatus"
        to:
          type: string
        subject:
          type: string
        queue_id:
          type:
            - string
            - "null"
        created_at:
          type: string
          format: date-time
      required:
        - id
        - status
        - to
        - subject
        - queue_id
        - created_at
    FunctionTestRunTrace:
      type: object
      description: |
        End-to-end trace for a `POST /functions/{id}/test` run. The
        shape is stable, but many nested sections are null or empty
        until the corresponding phase has happened.
      properties:
        state:
          $ref: "#/components/schemas/FunctionTestRunState"
        test_run:
          $ref: "#/components/schemas/FunctionTestRun"
        test_send:
          $ref: "#/components/schemas/FunctionTestRunSend"
        inbound_email:
          $ref: "#/components/schemas/FunctionTestRunInboundEmail"
        deliveries:
          type: array
          items:
            $ref: "#/components/schemas/FunctionTestRunDelivery"
        outbound_requests:
          type: array
          items:
            $ref: "#/components/schemas/FunctionTestRunOutboundRequest"
        logs:
          type: array
          items:
            $ref: "#/components/schemas/FunctionLogRow"
        replies:
          type: array
          items:
            $ref: "#/components/schemas/FunctionTestRunReply"
      required:
        - state
        - test_run
        - test_send
        - inbound_email
        - deliveries
        - outbound_requests
        - logs
        - replies
    FunctionLogRow:
      type: object
      description: |
        One row from GET /functions/{id}/logs. Represents a single
        captured log line emitted by the running handler (e.g. via
        `console.log` / `console.error`).
      properties:
        id:
          type: string
          format: uuid
          description: Unique log row id (stable across pages).
        function_id:
          type: string
          format: uuid
          description: The function this log row belongs to.
        level:
          type: string
          enum:
            - debug
            - log
            - info
            - warn
            - error
          description: |
            Severity. `log` is the runtime's default for unannotated
            `console.log` calls; the other levels match standard
            `console.*` methods.
        message:
          type: string
          description: |
            The textual message body. The runtime stringifies non-string
            arguments before persisting, so this is always a plain
            string.
        ts:
          type: string
          format: date-time
          description: |
            When the handler emitted this line. Newest-first ordering
            on this column drives pagination; clock is the runtime's,
            not the gateway's.
        metadata:
          type:
            - object
            - "null"
          additionalProperties: true
          description: |
            Optional structured payload the runtime attaches alongside
            the message (e.g. extra args passed to `console.log`).
            Shape is opaque; treat keys as untyped.
      required:
        - id
        - function_id
        - level
        - message
        - ts
    FunctionSecretListItem:
      type: object
      description: |
        One row from GET /functions/{id}/secrets. Discriminate on the
        `managed` field:
          * `managed = true`  — system secret provisioned by Primitive.
            `description` is set; `created_at` / `updated_at` are
            null because the row is virtual (resolved at deploy time
            from the managed registry, not stored in the secrets
            table).
          * `managed = false` — secret the user set via the API.
            `created_at` / `updated_at` are set; `description` is
            null.
      properties:
        key:
          type: string
        managed:
          type: boolean
          description: True for managed system secrets, false for user-set entries.
        description:
          type:
            - string
            - "null"
          description: Set on managed entries only; null on user-set entries.
        created_at:
          type:
            - string
            - "null"
          format: date-time
          description: Set on user-set entries only; null on managed entries.
        updated_at:
          type:
            - string
            - "null"
          format: date-time
          description: Set on user-set entries only; null on managed entries.
      required:
        - key
        - managed
    CreateFunctionSecretInput:
      type: object
      additionalProperties: false
      description: Body for POST /functions/{id}/secrets.
      properties:
        key:
          type: string
          pattern: ^[A-Z_][A-Z0-9_]*$
          description: |
            Uppercase letters, digits, and underscores. Must start with
            a letter or underscore. System-managed keys (e.g.
            PRIMITIVE_WEBHOOK_SECRET, PRIMITIVE_API_KEY, and
            PRIMITIVE_API_BASE_URL) are reserved.
        value:
          type: string
          minLength: 1
          maxLength: 4096
          description: |
            Secret value, up to 4096 UTF-8 bytes. Encrypted at rest.
            Never returned by any read endpoint.
      required:
        - key
        - value
    SetFunctionSecretInput:
      type: object
      additionalProperties: false
      description: Body for PUT /functions/{id}/secrets/{key}. Key comes from the path.
      properties:
        value:
          type: string
          minLength: 1
          maxLength: 4096
      required:
        - value
    FunctionSecretWriteResult:
      type: object
      description: Returned by POST and PUT secret routes.
      properties:
        key:
          type: string
        created_at:
          type: string
          format: date-time
        updated_at:
          type: string
          format: date-time
        created:
          type: boolean
          description: True if this call inserted a new row, false if it updated an existing one.
      required:
        - key
        - created_at
        - updated_at
        - created
    OrgSecretListItem:
      type: object
      description: |
        One row from GET /org/secrets. Org secrets are always user-set
        (there are no managed org secrets), so `created_at` /
        `updated_at` are always present.
      properties:
        key:
          type: string
        created_at:
          type: string
          format: date-time
        updated_at:
          type: string
          format: date-time
      required:
        - key
        - created_at
        - updated_at
    CreateOrgSecretInput:
      type: object
      additionalProperties: false
      description: Body for POST /org/secrets.
      properties:
        key:
          type: string
          pattern: ^[A-Z_][A-Z0-9_]*$
          description: |
            Uppercase letters, digits, and underscores. Must start with
            a letter or underscore. System-managed keys are reserved.
        value:
          type: string
          minLength: 1
          maxLength: 4096
          description: |
            Secret value, up to 4096 UTF-8 bytes. Encrypted at rest.
            Never returned by any read endpoint.
      required:
        - key
        - value
    SetOrgSecretInput:
      type: object
      additionalProperties: false
      description: Body for PUT /org/secrets/{key}. Key comes from the path.
      properties:
        value:
          type: string
          minLength: 1
          maxLength: 4096
      required:
        - value
    OrgSecretWriteResult:
      type: object
      description: Returned by POST and PUT org secret routes.
      properties:
        key:
          type: string
        created_at:
          type: string
          format: date-time
        updated_at:
          type: string
          format: date-time
        created:
          type: boolean
          description: True if this call inserted a new row, false if it updated an existing one.
      required:
        - key
        - created_at
        - updated_at
        - created
    NumericString:
      type: string
      pattern: ^[0-9]+$
      description: Bigint counter serialized as a base-10 string.
    MemoryJsonValue:
      description: JSON value accepted by Primitive Memories. The server accepts strings, numbers, booleans, null, arrays, and objects, validates nested values, and rejects values that do not serialize as JSON.
      oneOf:
        - type: "null"
        - type: string
        - type: number
        - type: boolean
        - type: array
          items:
            $ref: "#/components/schemas/MemoryJsonValue"
        - type: object
          additionalProperties:
            $ref: "#/components/schemas/MemoryJsonValue"
    MemoryScope:
      description: Memory scope. `org` resolves to the authenticated organization. `function` requires the function id UUID in `id`; function names are not valid scope identifiers. Function-authenticated requests cannot override their own Function scope.
      oneOf:
        - type: object
          additionalProperties: false
          properties:
            type:
              type: string
              enum:
                - org
          required:
            - type
        - type: object
          additionalProperties: false
          properties:
            type:
              type: string
              enum:
                - function
            id:
              type: string
              format: uuid
              description: Function id UUID.
          required:
            - type
            - id
    MemoryResolvedScope:
      type: object
      additionalProperties: false
      description: Resolved memory scope returned by the API.
      properties:
        type:
          type: string
          enum:
            - org
            - function
        id:
          type: string
          format: uuid
          description: Org id for org scope, function id for function scope.
      required:
        - type
        - id
    MemoryRecord:
      type: object
      additionalProperties: false
      description: Metadata for a Primitive memory. Search responses omit `value` when `include_value=false`.
      properties:
        id:
          type: string
          format: uuid
        key:
          type: string
          minLength: 1
          maxLength: 512
          description: Caller-defined key, at most 512 UTF-8 bytes.
        scope:
          $ref: "#/components/schemas/MemoryResolvedScope"
        value:
          $ref: "#/components/schemas/MemoryJsonValue"
        version:
          $ref: "#/components/schemas/NumericString"
        created_at:
          type: string
          format: date-time
        updated_at:
          type: string
          format: date-time
        last_read_at:
          type:
            - string
            - "null"
          format: date-time
          description: Last successful get timestamp, or null before any get.
        read_count:
          $ref: "#/components/schemas/NumericString"
        write_count:
          $ref: "#/components/schemas/NumericString"
        expires_at:
          type:
            - string
            - "null"
          format: date-time
          description: Expiration timestamp, or null for no TTL.
        created_by:
          type:
            - string
            - "null"
          description: Actor that created the memory, when available.
        updated_by:
          type:
            - string
            - "null"
          description: Actor that last updated the memory, when available.
      required:
        - id
        - key
        - scope
        - version
        - created_at
        - updated_at
        - last_read_at
        - read_count
        - write_count
        - expires_at
        - created_by
        - updated_by
    MemoryRecordWithValue:
      type: object
      additionalProperties: false
      description: Memory record returned by get and set operations.
      properties:
        id:
          type: string
          format: uuid
        key:
          type: string
          minLength: 1
          maxLength: 512
          description: Caller-defined key, at most 512 UTF-8 bytes.
        scope:
          $ref: "#/components/schemas/MemoryResolvedScope"
        value:
          $ref: "#/components/schemas/MemoryJsonValue"
        version:
          $ref: "#/components/schemas/NumericString"
        created_at:
          type: string
          format: date-time
        updated_at:
          type: string
          format: date-time
        last_read_at:
          type:
            - string
            - "null"
          format: date-time
          description: Last successful get timestamp, or null before any get.
        read_count:
          $ref: "#/components/schemas/NumericString"
        write_count:
          $ref: "#/components/schemas/NumericString"
        expires_at:
          type:
            - string
            - "null"
          format: date-time
          description: Expiration timestamp, or null for no TTL.
        created_by:
          type:
            - string
            - "null"
          description: Actor that created the memory, when available.
        updated_by:
          type:
            - string
            - "null"
          description: Actor that last updated the memory, when available.
      required:
        - id
        - key
        - scope
        - value
        - version
        - created_at
        - updated_at
        - last_read_at
        - read_count
        - write_count
        - expires_at
        - created_by
        - updated_by
    SetMemoryInput:
      type: object
      additionalProperties: false
      properties:
        key:
          type: string
          minLength: 1
          maxLength: 512
          description: Caller-defined key, at most 512 UTF-8 bytes.
        value:
          $ref: "#/components/schemas/MemoryJsonValue"
        scope:
          $ref: "#/components/schemas/MemoryScope"
        ttl_seconds:
          type: integer
          minimum: 1
          maximum: 31536000
          description: Set or replace the TTL in seconds. Mutually exclusive with `expires_at` and `clear_ttl`.
        expires_at:
          type: string
          format: date-time
          description: Set or replace the absolute expiration timestamp. Mutually exclusive with `ttl_seconds` and `clear_ttl`.
        clear_ttl:
          type: boolean
          description: Clear any existing TTL. Mutually exclusive with `ttl_seconds` and `expires_at`.
        if_absent:
          type: boolean
          description: Create only when the key is absent. Mutually exclusive with `if_version`.
        if_version:
          $ref: "#/components/schemas/NumericString"
      required:
        - key
        - value
    DeleteMemoryResult:
      type: object
      additionalProperties: false
      properties:
        deleted:
          type: boolean
        key:
          type: string
        scope:
          $ref: "#/components/schemas/MemoryResolvedScope"
      required:
        - deleted
        - key
        - scope
    TemplateAuthor:
      type: object
      additionalProperties: false
      properties:
        id:
          type: string
          minLength: 1
        name:
          type: string
          minLength: 1
        url:
          type: string
          format: uri
      required:
        - id
        - name
    TemplateSource:
      oneOf:
        - type: object
          additionalProperties: false
          properties:
            mode:
              type: string
              const: managed-build
            dir:
              type: string
              minLength: 1
              default: .
          required:
            - mode
            - dir
        - type: object
          additionalProperties: false
          properties:
            mode:
              type: string
              const: bundle
            file:
              type: string
              minLength: 1
          required:
            - mode
            - file
    TemplateInstall:
      type: object
      additionalProperties: false
      properties:
        mode:
          type: string
          enum:
            - deploy
            - scaffold
        editFiles:
          type: array
          items:
            type: string
            minLength: 1
          default: []
        reason:
          type: string
          default: ""
      required:
        - mode
        - editFiles
        - reason
    TemplateSecret:
      type: object
      additionalProperties: false
      properties:
        key:
          type: string
          pattern: ^[A-Z_][A-Z0-9_]*$
        required:
          type: boolean
          default: true
        description:
          type: string
      required:
        - key
        - required
    TemplateSecretGroup:
      type: object
      additionalProperties: false
      properties:
        keys:
          type: array
          minItems: 2
          items:
            type: string
            pattern: ^[A-Z_][A-Z0-9_]*$
        min:
          type: integer
          minimum: 1
          default: 1
        description:
          type: string
      required:
        - keys
        - min
    TemplateVariableValidation:
      type: object
      additionalProperties: false
      properties:
        pattern:
          type: string
          minLength: 1
        maxLength:
          type: integer
          minimum: 1
    TemplateVariable:
      type: object
      additionalProperties: false
      properties:
        key:
          type: string
          minLength: 1
        prompt:
          type: string
          minLength: 1
        default:
          type: string
        file:
          type: string
          minLength: 1
        type:
          type: string
          enum:
            - string
            - select
            - url
            - email
          default: string
        options:
          type: array
          items:
            type: string
            minLength: 1
        validation:
          $ref: "#/components/schemas/TemplateVariableValidation"
      required:
        - key
        - prompt
        - type
    TemplateSetup:
      type: object
      additionalProperties: false
      properties:
        agent:
          type: string
          minLength: 1
        prompt:
          type: string
          minLength: 1
        produces:
          type: array
          items:
            type: string
            minLength: 1
          default: []
      required:
        - agent
        - prompt
        - produces
    TemplateVendorConsumption:
      type: object
      additionalProperties: false
      properties:
        slug:
          type: string
          minLength: 1
        required:
          type: boolean
          default: true
      required:
        - slug
    TemplateManifest:
      type: object
      additionalProperties: false
      properties:
        schemaVersion:
          type: integer
          const: 1
        id:
          type: string
          pattern: ^[a-z0-9][a-z0-9_-]{0,62}$
          description: Stable template slug from the manifest.
        title:
          type: string
          minLength: 1
        summary:
          type: string
          minLength: 1
        description:
          type: string
        author:
          $ref: "#/components/schemas/TemplateAuthor"
        tags:
          type: array
          items:
            type: string
            minLength: 1
          default: []
        source:
          $ref: "#/components/schemas/TemplateSource"
        install:
          $ref: "#/components/schemas/TemplateInstall"
        secrets:
          type: array
          items:
            $ref: "#/components/schemas/TemplateSecret"
          default: []
        secretGroups:
          type: array
          items:
            $ref: "#/components/schemas/TemplateSecretGroup"
          default: []
        variables:
          type: array
          items:
            $ref: "#/components/schemas/TemplateVariable"
          default: []
        consumesVendors:
          type: array
          items:
            $ref: "#/components/schemas/TemplateVendorConsumption"
          default: []
        setup:
          $ref: "#/components/schemas/TemplateSetup"
        postInstall:
          type: string
      required:
        - schemaVersion
        - id
        - title
        - summary
        - author
        - tags
        - source
        - install
        - secrets
        - secretGroups
        - variables
    TemplateRegistryStatus:
      type: string
      enum:
        - pending
        - approved
        - rejected
    TemplateRegistrySummary:
      type: object
      properties:
        id:
          type: string
          format: uuid
        slug:
          type: string
          pattern: ^[a-z0-9][a-z0-9_-]{0,62}$
          description: Stable template slug used in template URLs and install commands.
        title:
          type: string
        summary:
          type: string
        author:
          $ref: "#/components/schemas/TemplateAuthor"
        tags:
          type: array
          items:
            type: string
        verified:
          type: boolean
        install_count:
          type: integer
          minimum: 0
        github_repo:
          type: string
          pattern: ^[A-Za-z0-9-]+/[A-Za-z0-9_.-]+$
          description: GitHub repository in owner/repo form.
        created_at:
          type: string
          format: date-time
        updated_at:
          type: string
          format: date-time
      required:
        - id
        - slug
        - title
        - summary
        - author
        - tags
        - verified
        - install_count
        - github_repo
        - created_at
        - updated_at
    TemplateRegistryDetail:
      type: object
      properties:
        id:
          type: string
          format: uuid
        slug:
          type: string
          pattern: ^[a-z0-9][a-z0-9_-]{0,62}$
          description: Stable template slug used in template URLs and install commands.
        title:
          type: string
        summary:
          type: string
        author:
          $ref: "#/components/schemas/TemplateAuthor"
        tags:
          type: array
          items:
            type: string
        verified:
          type: boolean
        install_count:
          type: integer
          minimum: 0
        github_repo:
          type: string
          pattern: ^[A-Za-z0-9-]+/[A-Za-z0-9_.-]+$
          description: GitHub repository in owner/repo form.
        created_at:
          type: string
          format: date-time
        updated_at:
          type: string
          format: date-time
        description:
          type:
            - string
            - "null"
        github_sha:
          type: string
        github_path:
          type:
            - string
            - "null"
        manifest:
          $ref: "#/components/schemas/TemplateManifest"
        readme:
          type:
            - string
            - "null"
        status:
          $ref: "#/components/schemas/TemplateRegistryStatus"
      required:
        - id
        - slug
        - title
        - summary
        - author
        - tags
        - verified
        - install_count
        - github_repo
        - created_at
        - updated_at
        - description
        - github_sha
        - github_path
        - manifest
        - readme
        - status
    TemplateRegistryPage:
      type: object
      properties:
        items:
          type: array
          items:
            $ref: "#/components/schemas/TemplateRegistrySummary"
        next_cursor:
          type:
            - string
            - "null"
          description: Cursor to pass as the next `cursor` query value, or null when there are no more templates.
      required:
        - items
        - next_cursor
  headers:
    IdempotencyKey:
      description: Effective idempotency key for this request. When the caller supplies an `Idempotency-Key` request header the same value is echoed; otherwise the server derives one from the canonical payload hash. Use this to correlate a request to its stored sent-email record via the `idempotency_key` filter on `GET /sent-emails`.
      schema:
        type: string
        minLength: 1
        maxLength: 255
    ratelimit-limit:
      description: Maximum number of requests allowed in the current window.
      schema:
        type: integer
        minimum: 1
        example: 120
    ratelimit-remaining:
      description: Remaining requests in the current window.
      schema:
        type: integer
        minimum: 0
        example: 118
    ratelimit-reset:
      description: Unix timestamp (seconds) when the current window resets.
      schema:
        type: integer
        example: 1700000060
    ratelimit-policy:
      description: Rate-limit policy in `limit;w=seconds` format, e.g. `120;w=60`.
      schema:
        type: string
        example: 120;w=60
