openapi: 3.1.0
info:
  title: tnl control API
  version: 1.0.0
  license:
    name: MIT
    identifier: MIT
jsonSchemaDialect: https://json-schema.org/draft/2020-12/schema
servers:
  - url: https://control.tnl.dev
paths:
  /v1/health:
    get:
      operationId: getHealth
      summary: Confirm that the control HTTP server is serving
      security: []
      responses:
        "200":
          description: Control HTTP server is serving
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/HealthResponse"
        default:
          $ref: "#/components/responses/Problem"
  /v1/ready:
    get:
      operationId: getReadiness
      summary: Confirm that the tnld role is ready to serve
      security: []
      responses:
        "200":
          description: The tnld role is ready
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ReadinessResponse"
        "503":
          description: The tnld role is not ready
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ReadinessResponse"
        default:
          $ref: "#/components/responses/Problem"
  /v1/client-ip:
    get:
      operationId: getClientIP
      summary: Return the requesting client's public IP address
      security: []
      responses:
        "200":
          description: Client IP address
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ClientIPResponse"
        default:
          $ref: "#/components/responses/Problem"
  /v1/discovery:
    get:
      operationId: getControlDiscovery
      summary: Describe control and its authentication methods
      security: []
      responses:
        "200":
          description: Control discovery
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ControlDiscovery"
        default:
          $ref: "#/components/responses/Problem"
  /v1/public-urls:
    get:
      operationId: listPublicURLs
      summary: List public URLs for one team
      security:
        - bearerAuth: []
      parameters:
        - $ref: "#/components/parameters/TeamIDQuery"
        - $ref: "#/components/parameters/Cursor"
        - $ref: "#/components/parameters/CanonicalHostnameQuery"
      responses:
        "200":
          description: PublicURL page
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/PublicURLPage"
        default:
          $ref: "#/components/responses/Problem"
    post:
      operationId: createPublicURL
      summary: Create one team public URL
      security:
        - bearerAuth: []
      parameters:
        - $ref: "#/components/parameters/IdempotencyKey"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/CreatePublicURLRequest"
      responses:
        "201":
          description: PublicURL created
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/PublicURL"
        default:
          $ref: "#/components/responses/Problem"
  /v1/public-urls/{public_url_id}:
    get:
      operationId: getPublicURL
      summary: Read one public URL
      security:
        - bearerAuth: []
      parameters:
        - $ref: "#/components/parameters/PublicURLID"
      responses:
        "200":
          description: PublicURL
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/PublicURL"
        default:
          $ref: "#/components/responses/Problem"
    patch:
      operationId: updatePublicURL
      summary: Update a public URL's target and IP policy
      security:
        - bearerAuth: []
      parameters:
        - $ref: "#/components/parameters/PublicURLID"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/UpdatePublicURLRequest"
      responses:
        "200":
          description: Updated public URL
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/PublicURL"
        default:
          $ref: "#/components/responses/Problem"
    delete:
      operationId: deletePublicURL
      summary: Delete one public URL and close its publish run
      security:
        - bearerAuth: []
      parameters:
        - $ref: "#/components/parameters/PublicURLID"
      responses:
        "204":
          description: PublicURL deleted
        default:
          $ref: "#/components/responses/Problem"
  /v1/public-urls/{public_url_id}/publish-runs:
    post:
      operationId: createPublishRun
      summary: Create a publish run and allocate its publish run number
      security:
        - bearerAuth: []
      parameters:
        - $ref: "#/components/parameters/PublicURLID"
        - $ref: "#/components/parameters/IdempotencyKey"
      responses:
        "201":
          description: PublicURL session and two publisher connection assignments
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/PublishRunSetup"
        default:
          $ref: "#/components/responses/Problem"
  /v1/publish-runs/{publish_run_id}/heartbeat:
    post:
      operationId: heartbeatPublishRun
      summary: Renew a publish run and replace missing publisher connections
      security:
        - publishRunAuth: []
      parameters:
        - $ref: "#/components/parameters/PublishRunID"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/PublishRunVersionRequest"
      responses:
        "200":
          description: Renewed publish run plan
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/PublishRunHeartbeat"
        default:
          $ref: "#/components/responses/Problem"
  /v1/publish-runs/{publish_run_id}/ready:
    post:
      operationId: markPublishRunReady
      summary: Mark a publish run ready after its certificate and two publisher connections are ready
      security:
        - publishRunAuth: []
      parameters:
        - $ref: "#/components/parameters/PublishRunID"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/PublishRunVersionRequest"
      responses:
        "200":
          description: Ready publish run
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/PublishRun"
        default:
          $ref: "#/components/responses/Problem"
  /v1/publish-runs/{publish_run_id}/certificate-installed:
    post:
      operationId: markPublishRunCertificateInstalled
      summary: Record installation of the current public URL certificate
      security:
        - publishRunAuth: []
      parameters:
        - $ref: "#/components/parameters/PublishRunID"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/CertificateInstalledRequest"
      responses:
        "200":
          description: Updated publish run
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/PublishRun"
        default:
          $ref: "#/components/responses/Problem"
  /v1/publish-runs/{publish_run_id}:
    delete:
      operationId: closePublishRun
      summary: Close and drain a publish run without deleting its public URL
      security:
        - publishRunAuth: []
      parameters:
        - $ref: "#/components/parameters/PublishRunID"
      responses:
        "204":
          description: PublicURL session closed
        default:
          $ref: "#/components/responses/Problem"
  /v1/publish-runs/{publish_run_id}/certificate-issuances:
    post:
      operationId: createCertificateIssuance
      summary: Begin issuance for the publish run's certificate plan
      security:
        - publishRunAuth: []
      parameters:
        - $ref: "#/components/parameters/PublishRunID"
        - $ref: "#/components/parameters/IdempotencyKey"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/CreateCertificateIssuanceRequest"
      responses:
        "201":
          description: Certificate issuance
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/CertificateIssuance"
        default:
          $ref: "#/components/responses/Problem"
  /v1/certificate-issuances/{issuance_id}:
    get:
      operationId: getCertificateIssuance
      summary: Read one publish-run certificate issuance
      security:
        - publishRunAuth: []
      parameters:
        - $ref: "#/components/parameters/IssuanceID"
      responses:
        "200":
          description: Certificate issuance
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/CertificateIssuance"
        default:
          $ref: "#/components/responses/Problem"
  /v1/certificate-issuances/{issuance_id}/challenge-ready:
    post:
      operationId: markCertificateChallengeReady
      summary: Confirm a TLS-ALPN-01 challenge is installed
      security:
        - publishRunAuth: []
      parameters:
        - $ref: "#/components/parameters/IssuanceID"
      responses:
        "200":
          description: Certificate issuance
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/CertificateIssuance"
        default:
          $ref: "#/components/responses/Problem"
  /v1/certificate-issuances/{issuance_id}/challenge-removed:
    post:
      operationId: markCertificateChallengeRemoved
      summary: Confirm a TLS-ALPN-01 challenge is removed
      security:
        - publishRunAuth: []
      parameters:
        - $ref: "#/components/parameters/IssuanceID"
      responses:
        "200":
          description: Certificate issuance
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/CertificateIssuance"
        default:
          $ref: "#/components/responses/Problem"
  /v1/admin/status:
    get:
      operationId: getAdminServerStatus
      summary: Read control, ingress, and relay status
      security:
        - bearerAuth: []
      responses:
        "200":
          description: Server status
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/AdminServerStatus"
        default:
          $ref: "#/components/responses/Problem"
  /v1/admin/relays:
    get:
      operationId: listAdminRelays
      summary: List relay leases
      security:
        - bearerAuth: []
      parameters:
        - $ref: "#/components/parameters/Cursor"
      responses:
        "200":
          description: Relay lease page
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/AdminRelayPage"
        default:
          $ref: "#/components/responses/Problem"
  /v1/admin/relays/{relay_id}/drain:
    post:
      operationId: drainAdminRelay
      summary: Remove a matching relay lease from placement and begin draining
      security:
        - bearerAuth: []
      parameters:
        - $ref: "#/components/parameters/RelayID"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/AdminDrainRelayRequest"
      responses:
        "200":
          description: Draining relay lease
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/AdminRelayLease"
        default:
          $ref: "#/components/responses/Problem"
  /v1/admin/maintenance-controls:
    get:
      operationId: listMaintenanceControls
      summary: List maintenance controls
      security:
        - bearerAuth: []
      responses:
        "200":
          description: Maintenance controls
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: "#/components/schemas/MaintenanceControl"
        default:
          $ref: "#/components/responses/Problem"
  /v1/admin/maintenance-controls/{control_name}:
    put:
      operationId: setMaintenanceControl
      summary: Set one maintenance control
      security:
        - bearerAuth: []
      parameters:
        - $ref: "#/components/parameters/MaintenanceControlName"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/SetMaintenanceControlRequest"
      responses:
        "200":
          description: Maintenance control
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/MaintenanceControl"
        default:
          $ref: "#/components/responses/Problem"
  /v1/service/revoke:
    post:
      operationId: revokeHostedPolicy
      summary: Apply a policy revision from the external authority and close affected publish runs
      security:
        - hostedServiceAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/HostedPolicyRevocation"
      responses:
        "204":
          description: Policy revision committed or replayed
        default:
          $ref: "#/components/responses/Problem"
  /v1/service/dns-authorities:
    post:
      operationId: createDNSAuthority
      summary: Create a DNS authority for one claimed domain
      security:
        - hostedServiceAuth: []
      parameters:
        - $ref: "#/components/parameters/IdempotencyKey"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/CreateDNSAuthorityRequest"
      responses:
        "201":
          description: DNS authority created
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/DNSAuthority"
        default:
          $ref: "#/components/responses/Problem"
  /v1/service/dns-authorities/{dns_authority_reference}:
    get:
      operationId: getDNSAuthority
      summary: Read a claimed domain's DNS authority
      security:
        - hostedServiceAuth: []
      parameters:
        - $ref: "#/components/parameters/DNSAuthorityReference"
      responses:
        "200":
          description: DNS authority
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/DNSAuthority"
        default:
          $ref: "#/components/responses/Problem"
    delete:
      operationId: releaseDNSAuthority
      summary: Begin releasing a claimed domain's DNS authority
      security:
        - hostedServiceAuth: []
      parameters:
        - $ref: "#/components/parameters/DNSAuthorityReference"
        - $ref: "#/components/parameters/IdempotencyKey"
      responses:
        "202":
          description: DNS authority release accepted
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/DNSAuthority"
        default:
          $ref: "#/components/responses/Problem"
components:
  securitySchemes:
    bearerAuth:
      type: http
      scheme: bearer
      bearerFormat: tnl access token
    hostedServiceAuth:
      type: http
      scheme: bearer
      bearerFormat: tnl hosted service secret
    publishRunAuth:
      type: http
      scheme: bearer
      bearerFormat: tnl publish-run token
  parameters:
    IdempotencyKey:
      name: Idempotency-Key
      in: header
      required: true
      schema:
        $ref: "#/components/schemas/IdempotencyKey"
    Cursor:
      name: cursor
      in: query
      required: false
      schema:
        $ref: "#/components/schemas/ResourceID"
    TeamIDQuery:
      name: team_id
      in: query
      required: true
      schema:
        $ref: "#/components/schemas/TeamID"
    CanonicalHostnameQuery:
      name: canonical_hostname
      in: query
      required: false
      description: Exact canonical hostname within the selected team. Returns zero or one non-deleted public URL with no next cursor. Cannot be combined with cursor.
      schema:
        $ref: "#/components/schemas/CanonicalHostname"
    PublicURLID:
      name: public_url_id
      in: path
      required: true
      schema:
        $ref: "#/components/schemas/PublicURLID"
    PublishRunID:
      name: publish_run_id
      in: path
      required: true
      schema:
        $ref: "#/components/schemas/PublishRunID"
    IssuanceID:
      name: issuance_id
      in: path
      required: true
      schema:
        $ref: "#/components/schemas/IssuanceID"
    RelayID:
      name: relay_id
      in: path
      required: true
      schema:
        $ref: "#/components/schemas/RelayID"
    MaintenanceControlName:
      name: control_name
      in: path
      required: true
      schema:
        $ref: "#/components/schemas/MaintenanceControlName"
    DNSAuthorityReference:
      name: dns_authority_reference
      in: path
      required: true
      schema:
        type: string
        minLength: 1
        maxLength: 256
        pattern: "^[A-Za-z0-9_-]+$"
  responses:
    Problem:
      description: Request failed
      content:
        application/problem+json:
          schema:
            $ref: "#/components/schemas/Problem"
  schemas:
    ResourceID:
      type: string
      minLength: 1
      maxLength: 256
      pattern: '^\S(?:.*\S)?$'
    IdentityID:
      $ref: "#/components/schemas/ResourceID"
    TeamID:
      $ref: "#/components/schemas/ResourceID"
    MembershipID:
      $ref: "#/components/schemas/ResourceID"
    DomainID:
      $ref: "#/components/schemas/ResourceID"
    PublicURLID:
      $ref: "#/components/schemas/ResourceID"
    PublishRunID:
      $ref: "#/components/schemas/ResourceID"
    PublisherConnectionID:
      $ref: "#/components/schemas/ResourceID"
    IssuanceID:
      $ref: "#/components/schemas/ResourceID"
    RelayServiceID:
      $ref: "#/components/schemas/ResourceID"
    RelayID:
      $ref: "#/components/schemas/ResourceID"
    IdempotencyKey:
      type: string
      minLength: 1
      maxLength: 128
      pattern: '^\S(?:.*\S)?$'
    CanonicalHostname:
      type: string
      minLength: 1
      maxLength: 253
      pattern: "^[a-z0-9](?:[a-z0-9.-]*[a-z0-9])?$"
    HealthResponse:
      type: object
      additionalProperties: false
      required: [status]
      properties:
        status:
          type: string
          const: ok
    ReadinessResponse:
      type: object
      additionalProperties: false
      required: [status, checks]
      properties:
        status:
          type: string
          enum: [ready, not_ready]
        checks:
          type: object
          additionalProperties: false
          required: [database, control]
          properties:
            database:
              type: string
              enum: [ok, failed]
            control:
              type: string
              description: Control listeners and public control certificate are ready.
              enum: [ok, failed]
            ingress:
              type: string
              description: Present in standalone; checks the ingress lease, routing table, and listener.
              enum: [ok, failed]
            relay:
              type: string
              description: Present in standalone; checks both relay services and their listeners.
              enum: [ok, failed]
            route53_credentials:
              type: string
              description: Present when control uses Route 53; checks that its AWS credentials can be obtained.
              enum: [ok, failed]
    ClientIPResponse:
      type: object
      additionalProperties: false
      required: [ip]
      properties:
        ip:
          type: string
          minLength: 2
          maxLength: 45
    ControlDiscovery:
      type: object
      additionalProperties: false
      required: [managed_deployment_domain, dns_automation, authority_endpoint, authentication]
      properties:
        managed_deployment_domain:
          $ref: "#/components/schemas/CanonicalHostname"
        dns_automation:
          type: boolean
        authority_endpoint:
          type: string
          format: uri
          pattern: "^https://"
        authentication:
          $ref: "#/components/schemas/AuthenticationFacts"
    AuthenticationFacts:
      type: object
      additionalProperties: false
      required: [methods]
      properties:
        methods:
          type: array
          minItems: 1
          uniqueItems: true
          items:
            type: string
            enum: [login_token, oidc]
        oidc:
          $ref: "#/components/schemas/OIDCAuthenticationFacts"
    OIDCAuthenticationFacts:
      type: object
      additionalProperties: false
      required: [issuer, client_id, login_flow, scopes]
      properties:
        issuer:
          type: string
          format: uri
        client_id:
          type: string
          minLength: 1
          maxLength: 128
        login_flow:
          type: string
          enum: [device_code, authorization_code_pkce]
        scopes:
          type: array
          minItems: 1
          uniqueItems: true
          items:
            type: string
            minLength: 1
            maxLength: 128
    HostedPolicyRevocation:
      type: object
      additionalProperties: false
      required: [team_id, policy_revision, all_sessions, membership_ids, domain_ids]
      properties:
        team_id:
          $ref: "#/components/schemas/TeamID"
        policy_revision:
          type: integer
          format: int64
          minimum: 1
        all_sessions:
          type: boolean
        membership_ids:
          type: array
          maxItems: 1000
          uniqueItems: true
          items:
            $ref: "#/components/schemas/MembershipID"
        domain_ids:
          type: array
          maxItems: 1000
          uniqueItems: true
          items:
            $ref: "#/components/schemas/DomainID"
    CreateDNSAuthorityRequest:
      type: object
      additionalProperties: false
      required: [team_id, domain_id, canonical_domain]
      properties:
        team_id:
          $ref: "#/components/schemas/TeamID"
        domain_id:
          $ref: "#/components/schemas/DomainID"
        canonical_domain:
          $ref: "#/components/schemas/CanonicalHostname"
    DNSAuthorityState:
      type: string
      enum: [pending, ready, releasing, released, failed]
    DNSRecord:
      type: object
      additionalProperties: false
      required: [name, type, value]
      properties:
        name:
          $ref: "#/components/schemas/CanonicalHostname"
        type:
          type: string
          enum: [NS, SOA, TXT, A, AAAA]
        value:
          type: string
          minLength: 1
          maxLength: 2048
    DNSAuthority:
      type: object
      additionalProperties: false
      required:
        [
          reference,
          team_id,
          domain_id,
          canonical_domain,
          state,
          required_records,
          created_at,
          updated_at,
        ]
      properties:
        reference:
          type: string
          minLength: 1
          maxLength: 256
        team_id:
          $ref: "#/components/schemas/TeamID"
        domain_id:
          $ref: "#/components/schemas/DomainID"
        canonical_domain:
          $ref: "#/components/schemas/CanonicalHostname"
        state:
          $ref: "#/components/schemas/DNSAuthorityState"
        required_records:
          type: array
          items:
            $ref: "#/components/schemas/DNSRecord"
        last_error:
          type: string
        created_at:
          type: string
          format: date-time
        updated_at:
          type: string
          format: date-time
    PublicURLScope:
      type: string
      enum: [member, shared]
    PublicURLLifecycleState:
      type: string
      enum: [enabled, suspended]
    PublicURL:
      type: object
      additionalProperties: false
      required:
        [
          id,
          team_id,
          domain_id,
          canonical_hostname,
          target,
          public_url_scope,
          policy_revision,
          lifecycle_state,
          next_publish_run_number,
          ephemeral,
          created_at,
          updated_at,
        ]
      properties:
        id:
          $ref: "#/components/schemas/PublicURLID"
        team_id:
          $ref: "#/components/schemas/TeamID"
        domain_id:
          $ref: "#/components/schemas/DomainID"
        membership_id:
          $ref: "#/components/schemas/MembershipID"
        canonical_hostname:
          $ref: "#/components/schemas/CanonicalHostname"
        target:
          type: string
          format: uri
          pattern: "^http://"
        public_url_scope:
          $ref: "#/components/schemas/PublicURLScope"
        policy_revision:
          type: integer
          format: int64
          minimum: 1
        lifecycle_state:
          $ref: "#/components/schemas/PublicURLLifecycleState"
        allowed_ip_prefixes:
          type: array
          maxItems: 64
          uniqueItems: true
          items:
            type: string
            minLength: 1
            maxLength: 64
            pattern: "^\\S+$"
        next_publish_run_number:
          type: integer
          format: int64
          minimum: 1
        ephemeral:
          type: boolean
        expires_at:
          type: string
          format: date-time
        open_publish_run_id:
          $ref: "#/components/schemas/PublishRunID"
        created_at:
          type: string
          format: date-time
        updated_at:
          type: string
          format: date-time
    PublicURLPage:
      type: object
      additionalProperties: false
      required: [public_urls]
      properties:
        public_urls:
          type: array
          items:
            $ref: "#/components/schemas/PublicURL"
        next_cursor:
          $ref: "#/components/schemas/PublicURLID"
    CreatePublicURLRequest:
      type: object
      additionalProperties: false
      required: [team_id, domain_id, canonical_hostname, target, public_url_scope]
      properties:
        team_id:
          $ref: "#/components/schemas/TeamID"
        domain_id:
          $ref: "#/components/schemas/DomainID"
        membership_id:
          $ref: "#/components/schemas/MembershipID"
        canonical_hostname:
          $ref: "#/components/schemas/CanonicalHostname"
        target:
          type: string
          format: uri
          pattern: "^http://"
        public_url_scope:
          $ref: "#/components/schemas/PublicURLScope"
        allowed_ip_prefixes:
          type: array
          maxItems: 64
          uniqueItems: true
          items:
            type: string
            minLength: 1
            maxLength: 64
            pattern: "^\\S+$"
        ephemeral:
          type: boolean
          default: false
    UpdatePublicURLRequest:
      type: object
      additionalProperties: false
      required: [target, allowed_ip_prefixes]
      properties:
        target:
          type: string
          format: uri
          pattern: "^http://"
        allowed_ip_prefixes:
          type: array
          maxItems: 64
          uniqueItems: true
          items:
            type: string
            minLength: 1
            maxLength: 64
            pattern: "^\\S+$"
    CertificateChallengeMethod:
      type: string
      enum: [dns-01, tls-alpn-01]
    CertificatePlan:
      type: object
      additionalProperties: false
      required: [cache_key, scope, identifiers, challenge_method]
      properties:
        cache_key:
          type: string
          minLength: 1
          maxLength: 256
        scope:
          type: string
          minLength: 1
          maxLength: 256
        identifiers:
          type: array
          minItems: 1
          maxItems: 2
          uniqueItems: true
          items:
            type: string
            minLength: 1
            maxLength: 253
            pattern: "^(?:\\*\\.)?[a-z0-9](?:[a-z0-9.-]*[a-z0-9])?$"
        challenge_method:
          $ref: "#/components/schemas/CertificateChallengeMethod"
    PublishRunState:
      type: string
      enum: [starting, ready, draining, closed, expired, canceled]
    PublishRun:
      type: object
      additionalProperties: false
      required:
        [
          id,
          public_url_id,
          team_id,
          publish_run_number,
          policy_revision,
          state,
          created_at,
          expires_at,
        ]
      properties:
        id:
          $ref: "#/components/schemas/PublishRunID"
        public_url_id:
          $ref: "#/components/schemas/PublicURLID"
        team_id:
          $ref: "#/components/schemas/TeamID"
        membership_id:
          $ref: "#/components/schemas/MembershipID"
        publish_run_number:
          type: integer
          format: int64
          minimum: 1
        policy_revision:
          type: integer
          format: int64
          minimum: 1
        state:
          $ref: "#/components/schemas/PublishRunState"
        ready_publisher_connections:
          type: integer
          minimum: 0
          maximum: 2
        created_at:
          type: string
          format: date-time
        expires_at:
          type: string
          format: date-time
        ready_at:
          type: string
          format: date-time
        closed_at:
          type: string
          format: date-time
    PublisherConnectionState:
      type: string
      enum: [assigned, connected, ready, replacing]
    ConnectionAssignment:
      type: object
      additionalProperties: false
      required:
        [
          connection_slot,
          publisher_connection_id,
          connection_assignment_revision,
          relay_service_id,
          relay_address,
          tls_server_name,
          publisher_connection_credential,
          publisher_connection_credential_expires_at,
          state,
        ]
      properties:
        connection_slot:
          type: integer
          minimum: 0
          maximum: 1
        publisher_connection_id:
          $ref: "#/components/schemas/PublisherConnectionID"
        connection_assignment_revision:
          type: integer
          format: int64
          minimum: 1
        relay_service_id:
          $ref: "#/components/schemas/RelayServiceID"
        relay_address:
          type: string
          minLength: 1
          maxLength: 256
        tls_server_name:
          $ref: "#/components/schemas/CanonicalHostname"
        publisher_connection_credential:
          type: string
          minLength: 1
          maxLength: 4096
        publisher_connection_credential_expires_at:
          type: string
          format: date-time
        state:
          $ref: "#/components/schemas/PublisherConnectionState"
    PublishRunSetup:
      type: object
      additionalProperties: false
      required:
        [public_url, publish_run, publish_run_token, certificate_plan, publisher_connections]
      properties:
        public_url:
          $ref: "#/components/schemas/PublicURL"
        publish_run:
          $ref: "#/components/schemas/PublishRun"
        publish_run_token:
          type: string
          minLength: 1
          maxLength: 4096
        certificate_plan:
          $ref: "#/components/schemas/CertificatePlan"
        publisher_connections:
          type: array
          minItems: 2
          maxItems: 2
          items:
            $ref: "#/components/schemas/ConnectionAssignment"
    PublishRunVersionRequest:
      type: object
      additionalProperties: false
      required: [publish_run_number]
      properties:
        publish_run_number:
          type: integer
          format: int64
          minimum: 1
    PublishRunHeartbeat:
      type: object
      additionalProperties: false
      required: [publish_run, publisher_connections, policy_denials]
      properties:
        publish_run:
          $ref: "#/components/schemas/PublishRun"
        publisher_connections:
          type: array
          minItems: 2
          maxItems: 2
          items:
            $ref: "#/components/schemas/ConnectionAssignment"
        policy_denials:
          description: Total IP policy denials reported so far for this publish run number. The total may lag.
          type: integer
          format: int64
          minimum: 0
    CertificateInstalledRequest:
      type: object
      additionalProperties: false
      required: [publish_run_number, issuance_id, not_after]
      properties:
        publish_run_number:
          type: integer
          format: int64
          minimum: 1
        issuance_id:
          $ref: "#/components/schemas/IssuanceID"
        not_after:
          type: string
          format: date-time
    CreateCertificateIssuanceRequest:
      type: object
      additionalProperties: false
      required: [publish_run_number, csr]
      properties:
        publish_run_number:
          type: integer
          format: int64
          minimum: 1
        csr:
          type: string
          format: byte
          minLength: 1
          maxLength: 65536
    CertificateIssuanceState:
      type: string
      enum:
        [
          pending,
          authorizing,
          ready_to_finalize,
          finalizing,
          waiting_for_install,
          installed,
          failed,
          canceled,
        ]
    CertificateChallenge:
      type: object
      additionalProperties: false
      required: [identifier, method, token, digest, expires_at]
      properties:
        identifier:
          $ref: "#/components/schemas/CanonicalHostname"
        method:
          $ref: "#/components/schemas/CertificateChallengeMethod"
        token:
          type: string
        digest:
          type: string
        expires_at:
          type: string
          format: date-time
    CertificateIssuance:
      type: object
      additionalProperties: false
      required:
        [
          id,
          publish_run_id,
          public_url_id,
          publish_run_number,
          certificate_plan,
          state,
          created_at,
          updated_at,
        ]
      properties:
        id:
          $ref: "#/components/schemas/IssuanceID"
        publish_run_id:
          $ref: "#/components/schemas/PublishRunID"
        public_url_id:
          $ref: "#/components/schemas/PublicURLID"
        publish_run_number:
          type: integer
          format: int64
          minimum: 1
        certificate_plan:
          $ref: "#/components/schemas/CertificatePlan"
        state:
          $ref: "#/components/schemas/CertificateIssuanceState"
        challenges:
          type: array
          items:
            $ref: "#/components/schemas/CertificateChallenge"
        certificate_pem:
          type: string
        retry_at:
          type: string
          format: date-time
        not_before:
          type: string
          format: date-time
        not_after:
          type: string
          format: date-time
        created_at:
          type: string
          format: date-time
        updated_at:
          type: string
          format: date-time
    AdminServerStatus:
      type: object
      additionalProperties: false
      required:
        [
          role,
          started_at,
          current_time,
          enabled_public_urls,
          suspended_public_urls,
          starting_publish_runs,
          ready_publish_runs,
          ingress_leases,
          relay_leases,
        ]
      properties:
        role:
          type: string
          enum: [standalone, control]
        started_at:
          type: string
          format: date-time
        current_time:
          type: string
          format: date-time
        enabled_public_urls:
          type: integer
          minimum: 0
        suspended_public_urls:
          type: integer
          minimum: 0
        starting_publish_runs:
          type: integer
          minimum: 0
        ready_publish_runs:
          type: integer
          minimum: 0
        ingress_leases:
          type: integer
          minimum: 0
        relay_leases:
          type: integer
          minimum: 0
    AdminRelayLease:
      type: object
      additionalProperties: false
      required:
        [
          relay_service_id,
          relay_id,
          relay_run_id,
          relay_lease_revision,
          protocol_version,
          relay_address,
          tls_server_name,
          internal_relay_address,
          connection_capacity,
          stream_capacity,
          reported_connections,
          reported_streams,
          draining,
          renewed_at,
          lease_expires_at,
        ]
      properties:
        relay_service_id:
          $ref: "#/components/schemas/RelayServiceID"
        relay_id:
          $ref: "#/components/schemas/RelayID"
        relay_run_id:
          $ref: "#/components/schemas/ResourceID"
        relay_lease_revision:
          type: integer
          format: int64
          minimum: 1
        protocol_version:
          type: integer
          format: int64
          minimum: 1
        relay_address:
          type: string
        tls_server_name:
          $ref: "#/components/schemas/CanonicalHostname"
        internal_relay_address:
          type: string
        connection_capacity:
          type: integer
          format: int64
          minimum: 1
        stream_capacity:
          type: integer
          format: int64
          minimum: 1
        reported_connections:
          type: integer
          format: int64
          minimum: 0
        reported_streams:
          type: integer
          format: int64
          minimum: 0
        draining:
          type: boolean
        drain_deadline:
          type: string
          format: date-time
        renewed_at:
          type: string
          format: date-time
        lease_expires_at:
          type: string
          format: date-time
    AdminRelayPage:
      type: object
      additionalProperties: false
      required: [relays]
      properties:
        relays:
          type: array
          items:
            $ref: "#/components/schemas/AdminRelayLease"
        next_cursor:
          $ref: "#/components/schemas/RelayID"
    AdminDrainRelayRequest:
      type: object
      additionalProperties: false
      required: [relay_run_id, relay_lease_revision, deadline]
      properties:
        relay_run_id:
          $ref: "#/components/schemas/ResourceID"
        relay_lease_revision:
          type: integer
          format: int64
          minimum: 1
        deadline:
          type: string
          format: date-time
    MaintenanceControlName:
      type: string
      enum: [public_url_creation, publish_run_creation, certificate_issuance]
    MaintenanceControl:
      type: object
      additionalProperties: false
      required: [name, allowed, revision, updated_at, updated_by]
      properties:
        name:
          $ref: "#/components/schemas/MaintenanceControlName"
        allowed:
          type: boolean
        revision:
          type: integer
          format: int64
          minimum: 1
        updated_at:
          type: string
          format: date-time
        updated_by:
          $ref: "#/components/schemas/IdentityID"
    SetMaintenanceControlRequest:
      type: object
      additionalProperties: false
      required: [allowed]
      properties:
        allowed:
          type: boolean
    ProblemCode:
      type: string
      enum:
        [
          invalid_request,
          unauthenticated,
          forbidden,
          not_found,
          conflict,
          rate_limited,
          unavailable,
          internal,
          name_unavailable,
          publish_run_open,
          policy_revision_stale,
          dns_setup_pending,
          placement_unavailable,
          issuance_retry,
        ]
    Problem:
      type: object
      additionalProperties: false
      required: [type, title, status, code, request_id]
      properties:
        type:
          type: string
          format: uri
        title:
          type: string
          minLength: 1
        status:
          type: integer
          minimum: 400
          maximum: 599
        code:
          $ref: "#/components/schemas/ProblemCode"
        request_id:
          type: string
          minLength: 1
        detail:
          type: string
        retry_at:
          type: string
          format: date-time
        details:
          type: object
          additionalProperties: true
