openapi: 3.1.0
info:
  title: tnl public URL usage API
  version: 1.0.0
  license:
    name: MIT
    identifier: MIT
jsonSchemaDialect: https://json-schema.org/draft/2020-12/schema
servers:
  - url: https://receiver.example
paths:
  /v1/public-urls/usage-bucket-reports:
    post:
      operationId: ingestPublicURLUsageBucketReports
      summary: Ingest public URL usage bucket reports
      security:
        - serviceToken: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/PublicURLUsageBucketReportBatch"
      responses:
        "200":
          description: Per-report ingestion results
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/PublicURLUsageBucketReportBatchResponse"
        "400":
          $ref: "#/components/responses/Problem"
        "401":
          $ref: "#/components/responses/Problem"
        "404":
          $ref: "#/components/responses/Problem"
        "409":
          $ref: "#/components/responses/Problem"
        "413":
          $ref: "#/components/responses/Problem"
        "415":
          $ref: "#/components/responses/Problem"
        default:
          $ref: "#/components/responses/Problem"
components:
  securitySchemes:
    serviceToken:
      type: http
      scheme: bearer
      bearerFormat: tnl service token
  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"
    PublicURLID:
      $ref: "#/components/schemas/ResourceID"
    PositiveInteger:
      type: string
      pattern: ^[1-9][0-9]{0,18}$
    UnsignedInteger:
      type: string
      pattern: ^(0|[1-9][0-9]{0,18})$
    DurationHistogram:
      type: object
      additionalProperties: false
      description: >-
        A cumulative duration histogram. cumulative_counts contains these fixed
        inclusive upper bounds, in order: 1ms, 5ms, 10ms, 25ms, 50ms, 100ms,
        250ms, 500ms, 1s, 2.5s, 5s, 10s, 30s, 1m, 5m, 15m, 1h, 6h, 24h, 3d,
        7d, and +Inf.
      required: [count, sum_nanoseconds, cumulative_counts]
      properties:
        count:
          $ref: "#/components/schemas/PositiveInteger"
        sum_nanoseconds:
          $ref: "#/components/schemas/UnsignedInteger"
        cumulative_counts:
          type: array
          minItems: 22
          maxItems: 22
          items:
            $ref: "#/components/schemas/UnsignedInteger"
    PublicURLUsageBucketReport:
      type: object
      additionalProperties: false
      required:
        - item_id
        - public_url_id
        - publish_run_number
        - team_id
        - acting_identity_id
        - resolution
        - bucket_start
        - report_revision
        - observed_through
        - connection_attempts
        - policy_denials
        - capacity_denials
        - visitor_stream_open_failures
        - successful_streams
        - connection_nanoseconds
        - ingress_bytes
        - egress_bytes
        - visitor_network_estimate
        - visitor_network_hll
        - complete
      properties:
        item_id:
          type: string
          pattern: ^usage_report_[0-9a-f]{32}$
        public_url_id:
          $ref: "#/components/schemas/PublicURLID"
        publish_run_number:
          $ref: "#/components/schemas/PositiveInteger"
          description: PublicURL version measured by this report.
        team_id:
          $ref: "#/components/schemas/TeamID"
          description: Team recorded when this publish run number was created.
        acting_identity_id:
          $ref: "#/components/schemas/IdentityID"
          description: Acting identity recorded when this publish run number was created.
        resolution:
          type: string
          enum: [minute, hour]
        bucket_start:
          type: string
          format: date-time
        report_revision:
          $ref: "#/components/schemas/PositiveInteger"
        observed_through:
          type: string
          format: date-time
          description: Latest time covered by this bucket. Equals the bucket end when complete is true.
        connection_attempts:
          $ref: "#/components/schemas/UnsignedInteger"
          description: Connections counted after a public URL match and before policy and capacity checks.
        policy_denials:
          $ref: "#/components/schemas/UnsignedInteger"
          description: Matched attempts rejected by public URL access policy.
        capacity_denials:
          $ref: "#/components/schemas/UnsignedInteger"
          description: Matched attempts rejected by the public URL connection limit.
        visitor_stream_open_failures:
          $ref: "#/components/schemas/UnsignedInteger"
          description: Attempts for which opening a visitor stream failed.
        successful_streams:
          $ref: "#/components/schemas/UnsignedInteger"
          description: Visitor streams that entered bidirectional forwarding.
        connection_nanoseconds:
          $ref: "#/components/schemas/UnsignedInteger"
          description: Elapsed time for successful streams, divided among the buckets in which the time passed.
        ingress_bytes:
          $ref: "#/components/schemas/UnsignedInteger"
          description: Bytes successfully forwarded from visitors to publishers.
        egress_bytes:
          $ref: "#/components/schemas/UnsignedInteger"
          description: Bytes successfully forwarded from publishers to visitors.
        visitor_stream_open_latency:
          allOf:
            - $ref: "#/components/schemas/DurationHistogram"
          description: >-
            Time taken to open a visitor stream, including failed attempts.
            The value is recorded when the attempt ends. The field is absent,
            rather than zero, when the bucket has no observations.
        time_to_first_publisher_byte:
          allOf:
            - $ref: "#/components/schemas/DurationHistogram"
          description: >-
            Time from a public URL match until the first publisher byte reaches the
            visitor. The value is recorded when that byte is written. The field
            is absent, rather than zero, when the bucket has no observations.
        successful_connection_duration:
          allOf:
            - $ref: "#/components/schemas/DurationHistogram"
          description: >-
            Total time for a successful visitor stream. The value is recorded
            when the stream closes. The field is absent, rather than zero, when
            the bucket has no observations.
        visitor_network_estimate:
          $ref: "#/components/schemas/UnsignedInteger"
          description: >-
            Precision-12 HyperLogLog estimate of distinct visitor networks for
            this public URL and bucket. All publish run numbers share the same sketch, so
            do not add estimates from reports for the same public URL and bucket.
        visitor_network_hll:
          type: string
          format: byte
          description: >-
            Versioned precision-12 HyperLogLog data. After base64 decoding, the
            bytes start with version 1, precision 12, and an encoding byte.
            Sparse encoding 0 then contains a big-endian uint16 entry count and
            sorted entries of a big-endian uint16 register index followed by a
            uint8 value. Dense encoding 1 contains 4096 uint8 registers. Inputs
            are visitor IPv4 /32 or IPv6 /64 networks HMACed with a public-URL-scoped
            secret that changes daily. No source address is included. When
            combining reports, merge each register by its maximum value instead
            of adding estimates.
        complete:
          type: boolean
          description: Whether accounting covers the entire bucket without a missing ingress report.
    BatchProblemCode:
      type: string
      enum: [invalid_argument, not_found, status_conflict, idempotency_conflict, internal]
    BatchResult:
      type: object
      additionalProperties: false
      required: [item_id, accepted]
      properties:
        item_id:
          type: string
          pattern: ^usage_report_[0-9a-f]{32}$
        accepted:
          type: boolean
        code:
          $ref: "#/components/schemas/BatchProblemCode"
    PublicURLUsageBucketReportBatch:
      type: object
      additionalProperties: false
      required: [items]
      properties:
        items:
          type: array
          minItems: 1
          maxItems: 32
          items:
            $ref: "#/components/schemas/PublicURLUsageBucketReport"
    PublicURLUsageBucketReportBatchResponse:
      type: object
      additionalProperties: false
      required: [results]
      properties:
        results:
          type: array
          minItems: 1
          maxItems: 32
          items:
            $ref: "#/components/schemas/BatchResult"
    Problem:
      type: object
      additionalProperties: false
      required: [type, title, status, code, request_id, details]
      properties:
        type:
          type: string
          format: uri
        title:
          type: string
          minLength: 1
          maxLength: 128
        status:
          type: integer
          minimum: 400
          maximum: 599
        code:
          type: string
          enum:
            - invalid_argument
            - unauthenticated
            - not_found
            - status_conflict
            - idempotency_conflict
            - temporarily_unavailable
            - internal
        request_id:
          type: string
          pattern: ^req_[A-Za-z0-9-]+$
        details:
          type: object
          maxProperties: 16
