# SPDX-License-Identifier: MIT OR Apache-2.0
#
# IDMX server-to-server API, v1-draft-00.
# Normative companions: delivery.md, discovery.md, signing.md, errors.md,
# capabilities.md.
openapi: 3.1.0
jsonSchemaDialect: https://json-schema.org/draft/2020-12/schema
info:
  title: IDMX — Inter-Domain Mail Exchange
  version: v1-draft-00
  summary: HTTPS-based inter-domain mail delivery (experimental draft).
  description: |
    The endpoint origin is found via SVCB on `_idmx.<domain>` (see discovery.md).
    TLS 1.3 is mandatory. Every request to `/v1/messages` carries an RFC 9421
    HTTP Message Signature by the sending domain (see signing.md).

    Evolution rules: the major version lives in the URL path; minor evolution
    is additive only; receivers and senders MUST ignore unknown JSON fields.
  license:
    name: MIT OR Apache-2.0
    identifier: MIT OR Apache-2.0
servers:
  - url: https://{idmxHost}
    description: Host discovered via SVCB on `_idmx.<recipient-domain>`.
    variables:
      idmxHost:
        default: idmx.example.org
tags:
  - name: delivery
  - name: capabilities

paths:
  /v1/messages:
    post:
      tags: [delivery]
      operationId: deliverMessage
      summary: Deliver one message to one or more recipients of a single domain.
      description: |
        One POST per recipient domain. The body is sent once; the response
        carries one result per recipient (mirrors SMTP RCPT semantics).

        The receiver SHOULD validate as much as possible synchronously
        (recipient exists, quota, policy) before returning 2xx. Failures after
        acceptance are reported as an RFC 3464 DSN delivered as a normal message.

        Body: `multipart/mixed` with exactly two parts, in order: part 1
        `application/json` envelope, part 2 `message/rfc822` raw message bytes
        (no transfer encoding). See delivery.md §2.
      parameters:
        - $ref: '#/components/parameters/IdempotencyKey'
        - $ref: '#/components/parameters/Signature'
        - $ref: '#/components/parameters/SignatureInput'
        - $ref: '#/components/parameters/ContentDigest'
      requestBody:
        required: true
        content:
          multipart/mixed:
            schema:
              type: object
              required: [envelope, message]
              properties:
                envelope:
                  $ref: '#/components/schemas/Envelope'
                message:
                  type: string
                  contentMediaType: message/rfc822
                  description: Opaque RFC 5322/MIME message, byte-for-byte. DKIM signatures inside survive.
            encoding:
              envelope:
                contentType: application/json
              message:
                contentType: message/rfc822
      responses:
        '200':
          description: |
            The request itself was acceptable; see per-recipient results
            (always 200, even if every recipient is rejected). A retried
            delivery with a known `Idempotency-Key` returns the original response.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/DeliveryResult'
        '400':
          $ref: '#/components/responses/Problem'
        '401':
          $ref: '#/components/responses/Problem'
        '403':
          $ref: '#/components/responses/Problem'
        '404':
          $ref: '#/components/responses/Problem'
        '409':
          $ref: '#/components/responses/Problem'
        '413':
          $ref: '#/components/responses/Problem'
        '429':
          $ref: '#/components/responses/ProblemRetryAfter'
        '503':
          $ref: '#/components/responses/ProblemRetryAfter'
        default:
          $ref: '#/components/responses/Problem'

  /v1/capabilities:
    get:
      tags: [capabilities]
      operationId: getCapabilities
      summary: Versions, limits, and operational parameters of this receiver.
      description: |
        Unsigned. No feature flags: v1 has no optional behavior
        (capabilities.md §2). Receivers SHOULD send
        `Cache-Control: max-age=3600`; senders assume 1 hour without it and
        never use a document older than 24 hours (capabilities.md §4). Each
        successful fetch sets or refreshes the sender's discovery pin.
      responses:
        '200':
          description: Capabilities document.
          headers:
            Cache-Control:
              schema:
                type: string
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Capabilities'
        default:
          $ref: '#/components/responses/Problem'

components:
  parameters:
    IdempotencyKey:
      name: Idempotency-Key
      in: header
      required: true
      description: |
        Covers the whole delivery (all recipients). Retries re-sign with a
        fresh timestamp but reuse the same key. Receivers remember keys for at
        least 7 days, scoped per signing
        domain. Same key with different content -> `idempotency_conflict`.
        See delivery.md §4.
      schema:
        type: string
        minLength: 1
        maxLength: 128
        pattern: '^[A-Za-z0-9._~-]+$'
    Signature:
      name: Signature
      in: header
      required: true
      description: RFC 9421 signature. Profile in signing.md.
      schema:
        type: string
    SignatureInput:
      name: Signature-Input
      in: header
      required: true
      description: RFC 9421 signature parameters (`created`, `keyid`, covered components). Profile in signing.md.
      schema:
        type: string
    ContentDigest:
      name: Content-Digest
      in: header
      required: true
      description: RFC 9530 digest of the request body, covered by the signature.
      schema:
        type: string

  responses:
    Problem:
      description: RFC 9457 problem details. Semantics per `type` in errors.md.
      content:
        application/problem+json:
          schema:
            $ref: '#/components/schemas/Problem'
    ProblemRetryAfter:
      description: RFC 9457 problem details with retry hint.
      headers:
        Retry-After:
          schema:
            type: string
      content:
        application/problem+json:
          schema:
            $ref: '#/components/schemas/Problem'

  schemas:
    Mailbox:
      type: string
      description: |
        `<local-part>@<domain>`, split at the last `@`. Domain in host-name
        syntax (IDNA A-labels). Local-part as written in SMTP (RFC 5321/6531
        dot-string or quoted-string), at most 64 octets, in minimal form: the
        pattern does not check the octet limit or that a quoted-string's
        content is not a dot-string. See delivery.md §3.1.
      maxLength: 320
      pattern: '^(?:[A-Za-z0-9!#$%&''*+/=?^_`{|}~\u00A0-\uFFFF-]+(?:\.[A-Za-z0-9!#$%&''*+/=?^_`{|}~\u00A0-\uFFFF-]+)*|"(?:[ !#-\[\]-~\u00A0-\uFFFF]|\\["\\])*")@[A-Za-z0-9.-]+$'

    Envelope:
      $id: https://idmx-project.org/schemas/v1/envelope
      description: |
        Structured delivery envelope (delivery.md §3). Unknown fields MUST be
        ignored (leaves room for later capabilities such as sender attestations).
      type: object
      required: [from, to]
      properties:
        from:
          description: |
            Envelope sender. A mailbox whose domain MUST equal the signing
            domain, or `null` for the null reverse-path (DSNs).
          oneOf:
            - $ref: '#/components/schemas/Mailbox'
            - type: 'null'
        to:
          type: array
          minItems: 1
          uniqueItems: true
          description: Recipients; all MUST share one domain. At most `max_recipients`.
          items:
            $ref: '#/components/schemas/Mailbox'
      additionalProperties: true

    DeliveryResult:
      $id: https://idmx-project.org/schemas/v1/delivery-result
      description: Per-recipient outcomes (delivery.md §5.2).
      type: object
      required: [results]
      properties:
        results:
          type: array
          description: One entry per envelope recipient, same order as `to`.
          items:
            $ref: '#/components/schemas/RecipientResult'
      additionalProperties: true

    RecipientResult:
      type: object
      required: [recipient, status]
      properties:
        recipient:
          $ref: '#/components/schemas/Mailbox'
        status:
          type: string
          description: |
            `accepted`; `rejected` (permanent, never retry, never SMTP
            fallback); `deferred` (temporary, retry as a new delivery). Senders
            MUST treat unknown values as `deferred`.
          examples: [accepted, rejected, deferred]
        problem:
          $ref: '#/components/schemas/Problem'
          description: REQUIRED unless status is `accepted`.
        retry_after:
          type: integer
          minimum: 0
          description: Seconds; only meaningful with `deferred`.
      additionalProperties: true

    Capabilities:
      $id: https://idmx-project.org/schemas/v1/capabilities
      description: |
        Versions, limits, and operational parameters (capabilities.md §3).
        Members MUST NOT announce optional protocol behavior. Unknown members
        MUST be ignored.
      type: object
      required: [max_message_size]
      properties:
        versions:
          type: array
          minItems: 1
          uniqueItems: true
          default: [v1]
          description: |
            All major versions this receiver serves, as URL path segments.
            Absent means `["v1"]`. Senders use the highest common version and
            MUST NOT probe version paths (discovery.md §5).
          items:
            type: string
            pattern: '^v[1-9][0-9]*$'
        max_message_size:
          type: integer
          description: |
            Largest accepted request body in bytes. At least 25 MiB; no upper
            limit. Over limit -> `message_too_large`.
          minimum: 26214400
        max_recipients:
          type: integer
          minimum: 100
          default: 100
          description: Largest accepted `to` length. Over limit -> `invalid_request`.
        discovery_pin_max_age:
          type: integer
          minimum: 0
          description: |
            Seconds a sender pins this domain's positive discovery result
            (discovery.md §3). `0` or absent removes the pin. Receivers SHOULD
            advertise 604800 (7 days); senders treat values above 31557600
            (1 year) as 31557600.
        abuse_contact:
          type: string
          format: uri
          pattern: '^mailto:[^?]+$'
          description: |
            Where to report abuse originating from the domains this receiver
            serves. One `mailto:` URI (RFC 6068) without header fields.
          examples:
            - mailto:abuse@receiver.example
      additionalProperties: true

    Problem:
      $id: https://idmx-project.org/schemas/v1/problem
      description: RFC 9457 problem details. `type` is `https://idmx-project.org/problems/<identifier>`.
      type: object
      required: [type]
      properties:
        type:
          type: string
          format: uri
          examples:
            - https://idmx-project.org/problems/invalid_request
            - https://idmx-project.org/problems/recipient_not_found
            - https://idmx-project.org/problems/invalid_signature
            - https://idmx-project.org/problems/unsupported_version
            - https://idmx-project.org/problems/temporary_failure
            - https://idmx-project.org/problems/rate_limited
            - https://idmx-project.org/problems/policy_rejected
            - https://idmx-project.org/problems/message_too_large
            - https://idmx-project.org/problems/idempotency_conflict
            - https://idmx-project.org/problems/mailbox_full
        title:
          type: string
        status:
          type: integer
        detail:
          type: string
        instance:
          type: string
        versions:
          type: array
          uniqueItems: true
          description: |
            REQUIRED in `unsupported_version` problems, absent otherwise: all
            major versions this receiver serves, as in the capabilities
            document (discovery.md §5.1).
          items:
            type: string
            pattern: '^v[1-9][0-9]*$'
      additionalProperties: true
