openapi: 3.1.0
info:
  title: HTPBE PDF Verification API
  summary: Detect PDF modifications and analyze document authenticity
  description: |
    HTPBE API provides comprehensive PDF authenticity verification through forensic analysis of metadata, structure, digital signatures, and content.

    **How it works:**
    1. Submit a public URL to your PDF (`POST /analyze`) — returns a check ID
    2. Retrieve the full forensic result with that ID (`GET /result/{id}`)
    3. List past checks with filtering (`GET /checks`)

    **Key Features:**
    - Accepts any publicly accessible HTTP/HTTPS PDF URL
    - Returns a three-state verdict: `intact`, `modified`, or `inconclusive`
    - Test keys available for integration testing (use mock URLs, no quota consumed)
    - Maximum file size: 10 MB

    **Authentication:**
    All API requests require an API key passed via the `Authorization` header.
    Supported formats:
    - `Authorization: Bearer htpbe_live_...`

    Get your API key at https://htpbe.tech/api

    **Key types:**
    - `htpbe_live_...` — production keys, count against monthly quota
    - `htpbe_test_...` — test keys, only work with mock URLs, free

  version: 3.0.0
  contact:
    name: HTPBE Team
    url: https://htpbe.tech
    email: mail@htpbe.tech
  license:
    name: Proprietary
    url: https://htpbe.tech/legal

servers:
  - url: https://api.htpbe.tech/v1
    description: Production server

security:
  - bearerAuth: []

tags:
  - name: Analysis
    description: Submit a PDF for forensic analysis
  - name: Results
    description: Retrieve analysis results and check history

components:
  securitySchemes:
    bearerAuth:
      type: http
      scheme: bearer
      bearerFormat: API Key
      description: |
        API key in Bearer token format.
        Example: `Authorization: Bearer htpbe_live_abc123`

        Get your API key at https://htpbe.tech/api

  schemas:

    # ─── Request ──────────────────────────────────────────────────────────────

    AnalyzeRequest:
      type: object
      required:
        - url
      properties:
        url:
          type: string
          format: uri
          description: |
            Publicly accessible HTTP or HTTPS URL pointing to a PDF file.
            The file must be downloadable without authentication and must not exceed 10 MB.
            Test keys only work with mock URLs (e.g. `https://api.htpbe.tech/v1/test/clean.pdf`).
          example: "https://example.com/documents/contract.pdf"
        original_filename:
          type: string
          description: Original filename of the document (before any storage renaming). Stored and returned in results.
          example: "contract_2024.pdf"

    # ─── Analyze response ─────────────────────────────────────────────────────

    AnalyzeResponse:
      type: object
      required:
        - id
      properties:
        id:
          type: string
          format: uuid
          description: Unique identifier for this check. Pass to `GET /result/{id}` to retrieve the full analysis.
          example: "3f9c8b7a-2e1d-4c5f-9b8e-7a6d5c4b3a21"

    # ─── Origin object ────────────────────────────────────────────────────────

    Origin:
      type: object
      required:
        - type
        - software
      properties:
        type:
          type: string
          enum: ["institutional", "consumer_software", "online_editor", "scanned", "unknown"]
          description: |
            Origin classification of the document.
            - `institutional` — created by dedicated institutional software (bank, government, enterprise)
            - `consumer_software` — created by a consumer application (Word, Excel, LibreOffice, Google Docs)
            - `online_editor` — processed through an online PDF editing service (iLovePDF, Smallpdf, PDF24, etc.)
            - `scanned` — a pure raster scan with no text layer (no fonts, no text operators, image-per-page structure)
            - `unknown` — origin cannot be determined
        software:
          type: string
          nullable: true
          description: Name of the detected software. Present when `type` is `consumer_software` (e.g. "Microsoft Excel") or `online_editor` (e.g. "iLovePDF"). Null for `institutional` and `unknown`.
          example: "Microsoft Excel"

    # ─── Result detail ────────────────────────────────────────────────────────

    ResultDetail:
      type: object
      required:
        - id
        - filename
        - check_date
        - file_size
        - algorithm_version
        - current_algorithm_version
        - status
        - origin
        - creation_date
        - modification_date
        - creator
        - producer
        - modification_confidence
        - date_sequence_valid
        - metadata_completeness_score
        - xref_count
        - has_incremental_updates
        - update_chain_length
        - pdf_version
        - has_digital_signature
        - signature_count
        - signature_removed
        - modifications_after_signature
        - page_count
        - object_count
        - has_javascript
        - has_embedded_files
        - modification_markers
      properties:
        id:
          type: string
          format: uuid
          description: Unique identifier for this check
          example: "506a6b1b-1360-48a2-b389-abb346f85d04"
        filename:
          type: string
          description: Filename of the analyzed PDF
          example: "contract.pdf"
        check_date:
          type: integer
          nullable: true
          description: Unix timestamp of when this check was performed. Null for test key results and legacy records.
          example: 1736542583
        file_size:
          type: integer
          description: File size in bytes
          example: 245632

        algorithm_version:
          type: string
          description: "Version of the detection algorithm used for this check (semantic versioning). Version numbers reflect the algorithm in use at the time of analysis. The current version may differ."
          example: "2.2.1"
        current_algorithm_version:
          type: string
          description: "Current algorithm version running on the server. Compare against algorithm_version to determine if the check is outdated. Version numbers reflect the algorithm in use at the time of analysis. The current version may differ."
          example: "2.2.1"
        outdated_warning:
          type: string
          description: Human-readable explanation of why re-analysis is recommended. Present only when `algorithm_version` is older than `current_algorithm_version` (semver less-than comparison).
          example: "Our detection software has been updated since this file was analyzed. The results shown below may no longer be accurate. We recommend re-analyzing this file for more accurate results."

        status:
          type: string
          enum: ["intact", "modified", "inconclusive"]
          description: |
            **Primary verdict.** Priority: `modified > inconclusive > intact`
            - `modified` — forensic evidence of post-creation modification detected
            - `inconclusive` — consumer software, online editor, or scanned origin with no modification detected; document cannot be verified for post-creation tampering
            - `intact` — no modification detected and origin appears institutional
          example: "modified"
        status_reason:
          type: string
          enum: ["consumer_software_origin", "online_editor_origin", "scanned_document"]
          description: Present only when `status` is `inconclusive`. Explains why the document cannot be verified.
        origin:
          $ref: '#/components/schemas/Origin'

        creation_date:
          type: integer
          nullable: true
          description: Unix timestamp of the PDF creation date from embedded metadata
          example: 1704110400
        modification_date:
          type: integer
          nullable: true
          description: Unix timestamp of the last modification date from embedded metadata
          example: 1707840000
        creator:
          type: string
          nullable: true
          description: Application that created the original document
          example: "Adobe Acrobat Pro DC"
        producer:
          type: string
          nullable: true
          description: Software that generated the final PDF
          example: "Adobe PDF Library 15.0"

        modification_confidence:
          type: string
          nullable: true
          enum: ["certain", "high", "none"]
          description: |
            Confidence level of the modification verdict.
            - `certain` — critical marker detected (signature removed, etc.)
            - `high` — multiple indicators present
            - `none` — no modification detected
          example: "certain"
        date_sequence_valid:
          type: boolean
          description: Whether the creation date precedes the modification date. `false` indicates date field tampering.
          example: true
        metadata_completeness_score:
          type: integer
          minimum: 0
          maximum: 100
          description: Completeness and quality score for the PDF metadata (0 = poor/missing, 100 = excellent)
          example: 90

        xref_count:
          type: integer
          description: Number of cross-reference tables. Multiple tables suggest post-creation modification.
          example: 2
        has_incremental_updates:
          type: boolean
          description: Whether the PDF contains incremental update sections — a common sign of modification.
          example: true
        update_chain_length:
          type: integer
          description: Number of incremental update sections. Values greater than 1 indicate post-creation modifications.
          example: 3
        pdf_version:
          type: string
          nullable: true
          description: PDF specification version (e.g. "1.4", "1.7", "2.0")
          example: "1.7"

        has_digital_signature:
          type: boolean
          description: Whether the PDF currently contains any digital signatures
          example: false
        signature_count:
          type: integer
          description: Number of digital signatures present
          example: 0
        signature_removed:
          type: boolean
          description: Whether a digital signature was removed from the document. Strong indicator of tampering.
          example: true
        modifications_after_signature:
          type: boolean
          description: Whether the document was modified after being digitally signed
          example: false

        page_count:
          type: integer
          description: Number of pages in the PDF
          example: 12
        object_count:
          type: integer
          description: Number of PDF objects in the document
          example: 487
        has_javascript:
          type: boolean
          description: Whether the PDF contains embedded JavaScript code
          example: false
        has_embedded_files:
          type: boolean
          description: Whether the PDF contains embedded file attachments
          example: false

        modification_markers:
          type: array
          items:
            type: string
          description: All modification signals detected, ordered strongest-first. Empty when status is intact or inconclusive.
          example:
            - "Digital signature was removed"
            - "Different creation and modification dates"

    # ─── Check summary (in /checks list) ─────────────────────────────────────

    CheckSummary:
      type: object
      required:
        - id
        - filename
        - check_date
        - status
        - metadata_completeness_score
        - creator
        - producer
        - file_size
        - page_count
        - pdf_version
        - creation_date
        - modification_date
        - has_javascript
        - has_digital_signature
        - has_embedded_files
        - has_incremental_updates
        - update_chain_length
        - object_count
      properties:
        id:
          type: string
          format: uuid
          description: Unique identifier for this check. Pass to `GET /result/{id}` for full details.
          example: "a3f5c9d2-1360-48a2-b389-abb346f85d04"
        filename:
          type: string
          description: Filename of the analyzed PDF
          example: "invoice-2024-01.pdf"
        check_date:
          type: integer
          nullable: true
          description: Unix timestamp of when this check was performed. Null for legacy records.
          example: 1738368000
        status:
          type: string
          enum: ["intact", "modified", "inconclusive"]
          description: Verdict for this check
          example: "modified"
        metadata_completeness_score:
          type: integer
          minimum: 0
          maximum: 100
          description: Metadata quality score (0–100)
          example: 85
        creator:
          type: string
          nullable: true
          description: Application that created the original document
          example: "Microsoft Word for Microsoft 365"
        producer:
          type: string
          nullable: true
          description: Software that generated the final PDF
          example: "Adobe PDF Library 15.0"
        file_size:
          type: integer
          description: File size in bytes
          example: 524288
        page_count:
          type: integer
          description: Number of pages
          example: 5
        pdf_version:
          type: string
          nullable: true
          description: PDF specification version
          example: "1.7"
        creation_date:
          type: integer
          nullable: true
          description: Unix timestamp of PDF creation date from metadata
          example: 1735689600
        modification_date:
          type: integer
          nullable: true
          description: Unix timestamp of last modification date from metadata
          example: 1738281600
        has_javascript:
          type: boolean
          description: Whether the PDF contains JavaScript
          example: false
        has_digital_signature:
          type: boolean
          description: Whether the PDF contains digital signatures
          example: true
        has_embedded_files:
          type: boolean
          description: Whether the PDF contains embedded file attachments
          example: false
        has_incremental_updates:
          type: boolean
          description: Whether the PDF contains incremental update sections
          example: true
        update_chain_length:
          type: integer
          description: Number of incremental update sections
          example: 3
        object_count:
          type: integer
          description: Number of PDF objects
          example: 234

    # ─── Checks list response ─────────────────────────────────────────────────

    ChecksResponse:
      type: object
      required:
        - data
        - total
        - limit
        - offset
        - has_more
      properties:
        data:
          type: array
          items:
            $ref: '#/components/schemas/CheckSummary'
          description: Array of check results for this page
        total:
          type: integer
          description: Total number of checks matching the filters (across all pages)
          example: 283
        limit:
          type: integer
          description: Number of results per page (echo of request parameter)
          example: 100
        offset:
          type: integer
          description: Number of results skipped (echo of request parameter)
          example: 0
        has_more:
          type: boolean
          description: Whether more results exist beyond this page (`offset + length(data) < total`)
          example: true

    # ─── Error response ───────────────────────────────────────────────────────

    ErrorResponse:
      type: object
      required:
        - error
        - code
      properties:
        error:
          type: string
          description: Human-readable error message
          example: "Invalid API key. Please check your credentials."
        code:
          type: string
          description: Machine-readable lowercase error code
          example: "invalid_api_key"
        details:
          type: string
          description: Additional error context (present for some errors)
          example: "limit must be between 1 and 500"

# ─── Paths ────────────────────────────────────────────────────────────────────

paths:

  /analyze:
    post:
      summary: Analyze a PDF for modifications
      description: |
        Submit a publicly accessible PDF URL for comprehensive forensic analysis.

        **Process (two steps):**
        1. `POST /analyze` — returns a check `id` immediately (201 Created)
        2. `GET /result/{id}` — retrieve the full forensic result using the returned `id`

        The `Location` response header points directly to the result URL.

        **Test keys** only work with mock URLs:
        - `https://api.htpbe.tech/v1/test/clean.pdf` — intact document
        - `https://api.htpbe.tech/v1/test/modified-high.pdf` — modified document

        See [Testing documentation](https://github.com/htpbe/docs/blob/main/testing.md) for the full list of 21 mock URLs and 1 error trigger.

        **Overage billing:** Requests beyond your monthly quota are charged at your plan's overage rate and billed automatically at the end of your billing cycle.

      operationId: analyzePDF
      tags:
        - Analysis
      security:
        - bearerAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/AnalyzeRequest'
            examples:
              basic:
                summary: Minimal request
                value:
                  url: "https://example.com/documents/invoice.pdf"
              with_filename:
                summary: With original filename
                value:
                  url: "https://storage.example.com/docs/abc123"
                  original_filename: "invoice_march_2024.pdf"
              test_clean:
                summary: Test key — intact document
                value:
                  url: "https://api.htpbe.tech/v1/test/clean.pdf"
              test_modified:
                summary: Test key — modified document
                value:
                  url: "https://api.htpbe.tech/v1/test/modified-high.pdf"

      responses:
        '201':
          description: Analysis completed — check ID returned
          headers:
            Location:
              description: Full URL of the result endpoint for this check
              schema:
                type: string
              example: "https://api.htpbe.tech/v1/result/3f9c8b7a-2e1d-4c5f-9b8e-7a6d5c4b3a21"
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/AnalyzeResponse'
              example:
                id: "3f9c8b7a-2e1d-4c5f-9b8e-7a6d5c4b3a21"
        '400':
          description: Bad request — invalid parameters, unsupported URL format, or failed download
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              examples:
                missing_url:
                  summary: Missing url field
                  value:
                    error: "Missing or invalid url parameter"
                    code: "invalid_request"
                invalid_url_format:
                  summary: URL is not a valid HTTP/HTTPS URL (including ftp://)
                  value:
                    error: "Invalid url format"
                    code: "invalid_url_format"
                invalid_json:
                  summary: Request body is not valid JSON
                  value:
                    error: "Invalid JSON body"
                    code: "invalid_request"
                download_failed:
                  summary: File could not be downloaded
                  value:
                    error: "Failed to download file from URL"
                    code: "download_failed"
                    details: "HTTP 404: Not Found"
        '401':
          description: Unauthorized — missing or invalid API key
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              examples:
                missing:
                  value:
                    error: "Missing API key. Please provide an API key in the Authorization header."
                    code: "missing_api_key"
                invalid:
                  value:
                    error: "Invalid API key. Please check your credentials."
                    code: "invalid_api_key"
        '402':
          description: Payment required — no active subscription
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              example:
                error: "No active subscription. Please subscribe to a plan to use the API."
                code: "payment_required"
        '403':
          description: Forbidden — API key deactivated, or test key used with non-test URL
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              examples:
                deactivated:
                  value:
                    error: "This API key has been deactivated. Please contact support."
                    code: "inactive_client"
                test_key_restriction:
                  value:
                    error: "Test API keys can only be used with test URLs. See documentation for available test URLs."
                    code: "test_url_required"
                    details: "Use URLs like: https://api.htpbe.tech/v1/test/clean.pdf, https://api.htpbe.tech/v1/test/modified-high.pdf, etc."
        '413':
          description: File too large — exceeds 10 MB limit
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              example:
                error: "File size exceeds limit"
                code: "file_too_large"
                details: "Maximum file size is 10 MB, received 15 MB"
        '422':
          description: Unprocessable — file is not a valid PDF
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              example:
                error: "Invalid PDF file"
                code: "invalid_pdf"
                details: "PDF header not found or file is corrupted"
        '500':
          description: Internal server error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              example:
                error: "Failed to analyze PDF"
                code: "internal_error"

  /result/{id}:
    get:
      summary: Get a single check result
      description: |
        Returns the full forensic detail of a specific past check by its ID.

        **Data isolation:** You can only retrieve results that belong to your API key.
        Attempting to access another client's results returns 404 (not 403) to prevent enumeration.

        **Test keys** can only access synthetic test IDs (e.g. `00000000-0000-4000-8000-000000000001`).
        Live keys can only access real check IDs created by their own API key.

      operationId: getResult
      tags:
        - Results
      security:
        - bearerAuth: []
      parameters:
        - name: id
          in: path
          required: true
          description: Check ID (UUID v4) returned by `POST /analyze`
          schema:
            type: string
            format: uuid
          example: "506a6b1b-1360-48a2-b389-abb346f85d04"

      responses:
        '200':
          description: Full forensic check detail
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ResultDetail'
              examples:
                modified:
                  summary: Modified document
                  value:
                    id: "506a6b1b-1360-48a2-b389-abb346f85d04"
                    filename: "contract.pdf"
                    check_date: 1736542583
                    file_size: 245632
                    algorithm_version: "2.2.1"
                    current_algorithm_version: "2.2.1"
                    status: "modified"
                    origin:
                      type: "institutional"
                      software: null
                    creation_date: 1704110400
                    modification_date: 1707840000
                    creator: "Adobe Acrobat Pro DC"
                    producer: "Adobe PDF Library 15.0"
                    modification_confidence: "certain"
                    date_sequence_valid: true
                    metadata_completeness_score: 90
                    xref_count: 2
                    has_incremental_updates: true
                    update_chain_length: 3
                    pdf_version: "1.7"
                    has_digital_signature: false
                    signature_count: 0
                    signature_removed: true
                    modifications_after_signature: false
                    page_count: 12
                    object_count: 487
                    has_javascript: false
                    has_embedded_files: false
                    modification_markers:
                      - "Digital signature was removed"
                      - "Different creation and modification dates"
                intact:
                  summary: Intact document (no modifications)
                  value:
                    id: "b2c3d4e5-f6a7-8901-bcde-f12345678901"
                    filename: "clean-report.pdf"
                    check_date: 1736542583
                    file_size: 524288
                    algorithm_version: "2.2.1"
                    current_algorithm_version: "2.2.1"
                    status: "intact"
                    origin:
                      type: "institutional"
                      software: null
                    creation_date: 1704110400
                    modification_date: null
                    creator: "Adobe Acrobat Pro DC"
                    producer: "Adobe PDF Library 15.0"
                    modification_confidence: "none"
                    date_sequence_valid: true
                    metadata_completeness_score: 75
                    xref_count: 1
                    has_incremental_updates: false
                    update_chain_length: 1
                    pdf_version: "1.7"
                    has_digital_signature: false
                    signature_count: 0
                    signature_removed: false
                    modifications_after_signature: false
                    page_count: 3
                    object_count: 120
                    has_javascript: false
                    has_embedded_files: false
                    modification_markers: []
                inconclusive:
                  summary: Inconclusive — consumer software origin
                  value:
                    id: "00000000-0000-4000-8000-000000000011"
                    filename: "inconclusive.pdf"
                    check_date: null
                    file_size: 204800
                    algorithm_version: "2.2.1"
                    current_algorithm_version: "2.2.1"
                    status: "inconclusive"
                    status_reason: "consumer_software_origin"
                    origin:
                      type: "consumer_software"
                      software: "Microsoft Excel"
                    creation_date: 1709283600
                    modification_date: 1709283600
                    creator: "Microsoft Excel 2019"
                    producer: "Microsoft: Print To PDF"
                    modification_confidence: "none"
                    date_sequence_valid: true
                    metadata_completeness_score: 65
                    xref_count: 1
                    has_incremental_updates: false
                    update_chain_length: 1
                    pdf_version: "1.7"
                    has_digital_signature: false
                    signature_count: 0
                    signature_removed: false
                    modifications_after_signature: false
                    page_count: 1
                    object_count: 82
                    has_javascript: false
                    has_embedded_files: false
                    modification_markers: []
                inconclusive_online_editor:
                  summary: Inconclusive — online editor origin
                  value:
                    id: "00000000-0000-4000-8000-000000000014"
                    filename: "inconclusive-online-editor.pdf"
                    check_date: null
                    file_size: 312000
                    algorithm_version: "2.2.1"
                    current_algorithm_version: "2.2.1"
                    status: "inconclusive"
                    status_reason: "online_editor_origin"
                    origin:
                      type: "online_editor"
                      software: "iLovePDF"
                    creation_date: null
                    modification_date: null
                    creator: null
                    producer: "iLovePDF"
                    modification_confidence: "none"
                    date_sequence_valid: true
                    metadata_completeness_score: 15
                    xref_count: 1
                    has_incremental_updates: false
                    update_chain_length: 1
                    pdf_version: "1.7"
                    has_digital_signature: false
                    signature_count: 0
                    signature_removed: false
                    modifications_after_signature: false
                    page_count: 2
                    object_count: 110
                    has_javascript: false
                    has_embedded_files: false
                    modification_markers: []
                inconclusive_scanned:
                  summary: Inconclusive — scanned document
                  value:
                    id: "00000000-0000-4000-8000-000000000015"
                    filename: "scanned-document.pdf"
                    check_date: null
                    file_size: 890000
                    algorithm_version: "2.2.1"
                    current_algorithm_version: "2.2.1"
                    status: "inconclusive"
                    status_reason: "scanned_document"
                    origin:
                      type: "scanned"
                      software: null
                    creation_date: null
                    modification_date: null
                    creator: null
                    producer: null
                    modification_confidence: "none"
                    date_sequence_valid: true
                    metadata_completeness_score: 0
                    xref_count: 1
                    has_incremental_updates: false
                    update_chain_length: 1
                    pdf_version: "1.4"
                    has_digital_signature: false
                    signature_count: 0
                    signature_removed: false
                    modifications_after_signature: false
                    page_count: 3
                    object_count: 45
                    has_javascript: false
                    has_embedded_files: false
                    modification_markers: []
        '400':
          description: Invalid check ID format
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              example:
                error: "Invalid check ID format"
                code: "invalid_request"
        '401':
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              example:
                error: "Missing API key. Please provide an API key in the Authorization header."
                code: "missing_api_key"
        '402':
          description: Payment required
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              example:
                error: "No active subscription. Please subscribe to a plan to use the API."
                code: "payment_required"
        '403':
          description: Forbidden — API key deactivated
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              example:
                error: "This API key has been deactivated. Please contact support."
                code: "inactive_client"
        '404':
          description: Check not found or belongs to a different API key
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              example:
                error: "Check not found or access denied"
                code: "not_found"
        '500':
          description: Server error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              example:
                error: "Failed to fetch result"
                code: "internal_error"

  /checks:
    get:
      summary: List past checks
      description: |
        Returns a paginated list of all PDF checks performed with your API key.
        Supports filtering by verdict status, date range, and creator/producer tool.

        Each item in `data` is a summary. For full forensic detail (modification markers, confidence level, origin, etc.), retrieve via `GET /result/{id}`.

        **Note:** Test keys are accepted but always return `{ data: [], total: 0 }` — test key requests do not write to the database, so no history is stored.

      operationId: listChecks
      tags:
        - Results
      security:
        - bearerAuth: []
      parameters:
        - name: limit
          in: query
          description: Number of results per page (default 100, max 500)
          schema:
            type: integer
            default: 100
            minimum: 1
            maximum: 500
          example: 100
        - name: offset
          in: query
          description: Number of results to skip for pagination (default 0)
          schema:
            type: integer
            default: 0
            minimum: 0
          example: 0
        - name: status
          in: query
          description: Filter by verdict status
          schema:
            type: string
            enum: ["intact", "modified", "inconclusive"]
          example: "modified"
        - name: tool
          in: query
          description: Filter by creator OR producer application name (exact match)
          schema:
            type: string
          example: "Adobe Acrobat Pro DC"
        - name: creator
          in: query
          description: Filter by creator application name (exact match)
          schema:
            type: string
          example: "Microsoft Word for Microsoft 365"
        - name: producer
          in: query
          description: Filter by producer application name (exact match)
          schema:
            type: string
          example: "Adobe PDF Library 15.0"
        - name: from_date
          in: query
          description: Filter checks by analysis date — Unix timestamp (inclusive lower bound)
          schema:
            type: integer
          example: 1735689600
        - name: to_date
          in: query
          description: Filter checks by analysis date — Unix timestamp (inclusive upper bound). Must be >= `from_date`.
          schema:
            type: integer
          example: 1738368000

      responses:
        '200':
          description: Paginated list of checks
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ChecksResponse'
              example:
                data:
                  - id: "a3f5c9d2-1360-48a2-b389-abb346f85d04"
                    filename: "invoice-2024-01.pdf"
                    check_date: 1738368000
                    status: "modified"
                    metadata_completeness_score: 85
                    creator: "Microsoft Word for Microsoft 365"
                    producer: "Adobe PDF Library 15.0"
                    file_size: 524288
                    page_count: 5
                    pdf_version: "1.7"
                    creation_date: 1735689600
                    modification_date: 1738281600
                    has_javascript: false
                    has_digital_signature: true
                    has_embedded_files: false
                    has_incremental_updates: true
                    update_chain_length: 3
                    object_count: 234
                total: 283
                limit: 100
                offset: 0
                has_more: true
        '400':
          description: Invalid filter or pagination parameters
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              examples:
                invalid_limit:
                  value:
                    error: "Invalid query parameters"
                    code: "invalid_request"
                    details: "limit must be between 1 and 500"
                invalid_status:
                  value:
                    error: "Invalid query parameters"
                    code: "invalid_request"
                    details: "status must be one of: intact, modified, inconclusive"
                date_range_invalid:
                  value:
                    error: "Invalid query parameters"
                    code: "invalid_request"
                    details: "from_date must be before or equal to to_date"
        '401':
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              example:
                error: "Missing API key. Please provide an API key in the Authorization header."
                code: "missing_api_key"
        '402':
          description: Payment required
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              example:
                error: "No active subscription. Please subscribe to a plan to use the API."
                code: "payment_required"
        '403':
          description: Forbidden — API key deactivated
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              example:
                error: "This API key has been deactivated. Please contact support."
                code: "inactive_client"
        '500':
          description: Server error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              example:
                error: "Failed to fetch checks"
                code: "internal_error"

externalDocs:
  description: Full API documentation with code examples
  url: https://htpbe.tech/api
