openapi: 3.0.3
info:
  title: Capture Service API
  version: unversioned
servers:
  - url: /
paths:
  /v1/capture:
    get:
      operationId: listCapturesByUrl
      summary: List captures by exact link (paged)
      description: |
        Returns a paged list of captures for the exact normalized link.

        Ordering and paging:
        - Items are ordered by created_at (descending), then by UUID (ascending).
        - The page_token is opaque; use next_page_token or previous_page_token from a prior response.
      parameters:
        - in: query
          name: link
          required: true
          description: Exact link to list. Accepts a full HTTP/HTTPS link or a link without scheme (host with optional path/query). The server normalizes scheme and host.
          schema:
            $ref: '../common/common.yaml#/components/schemas/Link'
          examples:
            absolute-link:
              value: https://example.com/a?b=1
            link-without-scheme:
              value: example.com/a?b=1
        - in: query
          name: page_token
          required: false
          schema:
            type: string
          description: Opaque token from a previous response to get the next or previous page.
      responses:
        '200':
          description: Paged list of captures (latest first).
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PagedFindCapturesByLinkResponse'
        '400':
          description: Bad request (invalid link or pagination parameters).
          content:
            application/json:
              schema:
                $ref: '../common/common.yaml#/components/schemas/ErrorEnvelope'
              examples:
                missing_link:
                  value:
                    error:
                      message: "link: missing parameter"
                bad_page_token:
                  value:
                    error:
                      message: "page_token: invalid page_token"
        '500':
          description: Internal server error.
          content:
            application/json:
              schema:
                $ref: '../common/common.yaml#/components/schemas/ErrorEnvelope'
    post:
      operationId: createCapture
      summary: Enqueue a capture job and return its status
      description: |
        Enqueues a background capture job for the provided link and returns an initial
        job descriptor. The returned UUID identifies the capture job. Once the job
        succeeds, the stored capture UUID is available in `result.uuid` and may
        refer to an existing capture if the content is deduplicated. Clients should poll
        `/v1/capture/jobs/{uuid}` to observe completion.

        When HTTPS-only mode is enabled, HTTP inputs are accepted but capture attempts
        and browser subresource fetches use HTTPS/WSS only.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/CreateCaptureRequest'
            examples:
              absolute-link:
                summary: Absolute HTTP/HTTPS link
                value:
                  link: https://example.com/path?q=1
              link-without-scheme:
                summary: Link without scheme
                value:
                  link: example.com/path?q=1
      responses:
        '202':
          description: Capture job enqueued; job descriptor returned.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/CaptureJob'
        '400':
          description: Invalid JSON or link supplied.
          content:
            application/json:
              schema:
                $ref: '../common/common.yaml#/components/schemas/ErrorEnvelope'
              examples:
                bad_request:
                  value:
                    error:
                      message: invalid request body
                bad_link:
                  value:
                    error:
                      message: invalid host
        '403':
          description: Link prefix is denied by access policy
          content:
            application/json:
              schema:
                $ref: '../common/common.yaml#/components/schemas/ErrorEnvelope'
              examples:
                denylisted:
                  value:
                    error:
                      message: link prefix in denylist
                not_allowlisted:
                  value:
                    error:
                      message: link not in allowlist
        '500':
          description: Internal server error.
          content:
            application/json:
              schema:
                $ref: '../common/common.yaml#/components/schemas/ErrorEnvelope'
  /v1/capture/prefix:
    get:
      operationId: listCapturesByPrefix
      summary: List capture UUIDs by link prefix
      description: |
        Returns captures for links whose normalized scheme-less form starts with the provided prefix.

        Ordering and paging:
        - Results are ordered by link (ascending), then by created_at (descending), then by UUID (ascending).
        - Each page includes up to N distinct links and up to M newest captures per link; N and M are server-configured caps.
        - The page_token is opaque; use next_page_token or previous_page_token from a prior response.
      parameters:
        - in: query
          name: prefix
          required: true
          description: Link prefix. Must include a full host; accepts a full HTTP/HTTPS link or a link without scheme.
          schema:
            $ref: '../common/common.yaml#/components/schemas/Link'
          examples:
            host-prefix:
              value: example.com/a
            absolute-prefix:
              value: https://example.com/a
        - in: query
          name: page_token
          required: false
          schema:
            type: string
          description: Opaque token from a previous response to get the next or previous page.
      responses:
        '200':
          description: Paged list of captures matching the prefix.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PagedFindCapturesByPrefixResponse'
        '400':
          description: Bad request (invalid prefix or pagination parameters).
          content:
            application/json:
              schema:
                $ref: '../common/common.yaml#/components/schemas/ErrorEnvelope'
              examples:
                missing_prefix:
                  value:
                    error:
                      message: "prefix: missing parameter"
                bad_page_token:
                  value:
                    error:
                      message: "page_token: invalid page_token"
        '500':
          description: Internal server error.
          content:
            application/json:
              schema:
                $ref: '../common/common.yaml#/components/schemas/ErrorEnvelope'
  /v1/capture/jobs/{uuid}:
    get:
      operationId: getCaptureJob
      summary: Get capture job status by UUID
      parameters:
        - in: path
          name: uuid
          required: true
          description: UUIDv4 of the capture job.
          schema:
            type: string
            format: uuid
      responses:
        '200':
          description: Job found; current status returned.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/CaptureJob'
        '400':
          description: Bad request (missing or invalid UUID path parameter).
          content:
            application/json:
              schema:
                $ref: '../common/common.yaml#/components/schemas/ErrorEnvelope'
              examples:
                invalid_uuid:
                  value:
                    error:
                      message: "uuid: invalid parameter"
        '404':
          description: Job not found.
          content:
            application/json:
              schema:
                $ref: '../common/common.yaml#/components/schemas/ErrorEnvelope'
              examples:
                not_found:
                  value:
                    error:
                      message: job not found
        '500':
          description: Internal server error.
          content:
            application/json:
              schema:
                $ref: '../common/common.yaml#/components/schemas/ErrorEnvelope'
  /v1/capture/{uuid}:
    get:
      operationId: getCaptureByUuid
      summary: Get capture metadata by UUID
      parameters:
        - in: path
          name: uuid
          required: true
          description: UUIDv4 of the capture.
          schema:
            type: string
            format: uuid
          example: 123e4567-e89b-12d3-a456-426614174000
      responses:
        '200':
          description: Capture found; metadata and anonymous-read storage URL returned.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/CaptureDetails'
        '400':
          description: Bad request (missing or invalid UUID path parameter).
          content:
            application/json:
              schema:
                $ref: '../common/common.yaml#/components/schemas/ErrorEnvelope'
              examples:
                missing_uuid:
                  value:
                    error:
                      message: "uuid: missing parameter"
        '404':
          description: Capture not found.
          content:
            application/json:
              schema:
                $ref: '../common/common.yaml#/components/schemas/ErrorEnvelope'
              examples:
                not_found:
                  value:
                    error:
                      message: capture not found
        '500':
          description: Internal server error.
          content:
            application/json:
              schema:
                $ref: '../common/common.yaml#/components/schemas/ErrorEnvelope'
components:
  schemas:
    PaginationCursor:
      type: object
      additionalProperties: false
      required: [t, i, d]
      description: Opaque pagination cursor; microsecond epoch, UUID, and direction of the boundary item
      properties:
        t:
          type: integer
          format: int64
          description: Microseconds since Unix epoch
          example: 1763037596000000
        i:
          type: string
          format: uuid
          description: UUID of the cursor item
        d:
          type: string
          enum: [next, previous]
          description: Direction to page from the cursor item
    CreateCaptureRequest:
      type: object
      additionalProperties: false
      required: [link]
      properties:
        link:
          $ref: '../common/common.yaml#/components/schemas/Link'
    UuidWithTime:
      type: object
      additionalProperties: false
      required: [uuid, created_at]
      properties:
        uuid:
          type: string
          format: uuid
          example: 123e4567-e89b-12d3-a456-426614174000
        created_at:
          type: string
          format: date-time
          example: 2025-11-11T12:34:56+00:00
    PagedFindCapturesByLinkResponse:
      type: object
      additionalProperties: false
      required: [items]
      properties:
        items:
          type: array
          items:
            $ref: '#/components/schemas/UuidWithTime'
        next_page_token:
          type: string
        previous_page_token:
          type: string
      example:
        items:
          - uuid: 123e4567-e89b-12d3-a456-426614174000
            created_at: 2025-11-11T12:34:56+00:00
        next_page_token: eyJ0IjoxNjk5ODMyMDAwMDAwLCJpIjoiMTIzZTQ1NjctZTg5Yi0xMmQzLWE0NTYtNDI2NjE0MTc0MDAwIn0
    UuidWithTimeLink:
      type: object
      additionalProperties: false
      required: [uuid, created_at, link]
      properties:
        uuid:
          type: string
          format: uuid
        created_at:
          type: string
          format: date-time
        link:
          type: string
      example:
        uuid: 123e4567-e89b-12d3-a456-426614174000
        created_at: 2025-11-11T12:34:56+00:00
        link: example.com/a
    CaptureJob:
      type: object
      additionalProperties: false
      required: [uuid, link, status, created_at]
      properties:
        uuid:
          type: string
          format: uuid
        link:
          type: string
          description: Normalized scheme-less link
        status:
          type: string
          description: Current job status
          enum: [pending, running, succeeded, failed]
        created_at:
          type: string
          format: date-time
        started_at:
          type: string
          format: date-time
        finished_at:
          type: string
          format: date-time
        result_created_at:
          type: string
          format: date-time
          description: created_at of the stored capture, when succeeded
        result:
          $ref: '#/components/schemas/UuidWithTimeLink'
        error:
          $ref: '../common/common.yaml#/components/schemas/ErrorEnvelope'
      example:
        uuid: 123e4567-e89b-12d3-a456-426614174000
        link: example.com/a
        status: pending
        created_at: 2025-11-11T12:34:56+00:00
    PagedFindCapturesByPrefixResponse:
      type: object
      additionalProperties: false
      required: [items]
      properties:
        items:
          type: array
          items:
            $ref: '#/components/schemas/UuidWithTimeLink'
        next_page_token:
          type: string
        previous_page_token:
          type: string
    CaptureDetails:
      type: object
      additionalProperties: false
      required: [uuid, created_at, link, storage_url]
      properties:
        uuid:
          type: string
          format: uuid
        created_at:
          type: string
          format: date-time
        link:
          type: string
          description: Normalized scheme-less link
        storage_url:
          type: string
          format: uri
          description: Anonymous-read direct S3 URL of the stored WACZ.
      example:
        uuid: 123e4567-e89b-12d3-a456-426614174000
        created_at: 2025-11-11T12:34:56+00:00
        link: example.com/a
        storage_url: https://storage.yandexcloud.net/webshot/123e4567-e89b-12d3-a456-426614174000
    PaginationPrefixCursor:
      type: object
      additionalProperties: false
      description: Opaque pagination cursor for prefix search
      required: [p, l, d]
      properties:
        p:
          type: string
          description: Normalized prefix
        l:
          type: string
          description: Last link processed
        d:
          type: string
          enum: [next, previous]
          description: Direction to page from the cursor position
        t:
          type: integer
          format: int64
          description: Microseconds since Unix epoch of last item
        i:
          type: string
          format: uuid
          description: UUID of last item when resuming mid-link
