UnifyComply

Documentation

API contract (OpenAPI)

This page is the human-friendly view: Swagger UI to browse endpoints, plus the YAML source for reading or copying.

The URL /docs/openapi.yaml is the machine-readable file the explorer loads automatically. It is meant for tools (Postman, Insomnia, OpenAPI Generator, CI) and normally downloads as a file— not as a full-screen “black page” in the browser.

Interactive reference

Swagger UI loads the contract from the same endpoint as the download link above.

Loading interactive API reference…

Source YAML

For diffs, codegen, or pasting into an editor. Expand to read inline with site styling.

Show full YAML
openapi: 3.1.0
info:
  title: ADA PDF Gatekeeper API
  version: "1.0.0"
  description: >
    REST API for in-memory PDF accessibility analysis and remediation. Designed for zero-storage,
    privacy-first integrations (CMS plugins, workers, backends).
    Recommended single-step flow: POST /v1/process returns analysis plus remediation in one response,
    with an optional temporary download URL for an accessibility-enhanced PDF.
    This process improves accessibility and helps meet ADA/WCAG guidelines, but does not constitute
    a formal compliance certification. Outputs are not PDF/UA guarantees and OCR-rebuilt documents
    may differ visually from the source.
    Error responses use `{ "error": true, "message": "..." }` unless noted.
    When `REQUIRE_API_KEY_FOR_BATCH_AND_REMEDIATE=false` on the server, `POST /v1/batch`,
    `POST /v1/remediate`, and `GET /v1/batch/{batch_id}` may be called without an API key
    (development only).

servers:
  - url: https://api.example.com
    description: Production
  - url: http://127.0.0.1:3002
    description: Local development

components:
  securitySchemes:
    apiKey:
      type: apiKey
      in: header
      name: X-API-Key

  schemas:
    ProcessingStatus:
      type: string
      enum: [processing, completed, failed]

    ComplianceStatus:
      type: string
      nullable: true
      enum: [compliant, partially_compliant, not_compliant]

    ScanIssue:
      type: object
      properties:
        id:
          type: string
        severity:
          type: string
          enum: [critical, warning, info]
        title:
          type: string
        description:
          type: string
        recommendation:
          type: string
      required: [id, severity, title, description, recommendation]

    ScanIssuesBySeverity:
      type: object
      properties:
        critical:
          type: array
          items:
            $ref: "#/components/schemas/ScanIssue"
        warning:
          type: array
          items:
            $ref: "#/components/schemas/ScanIssue"
        info:
          type: array
          items:
            $ref: "#/components/schemas/ScanIssue"
      required: [critical, warning, info]

    ScanSource:
      type: object
      properties:
        type:
          type: string
          enum: [upload, url]
        label:
          type: string
      required: [type, label]

    ScanResult:
      type: object
      properties:
        id:
          type: string
        source:
          $ref: "#/components/schemas/ScanSource"
        processing_status:
          $ref: "#/components/schemas/ProcessingStatus"
        compliance_status:
          $ref: "#/components/schemas/ComplianceStatus"
        score:
          type: integer
          format: int32
          nullable: true
        summary:
          type: string
          nullable: true
        issues:
          allOf:
            - $ref: "#/components/schemas/ScanIssuesBySeverity"
          nullable: true
        createdAt:
          type: string
          format: date-time
      required:
        [id, source, processing_status, compliance_status, score, summary, issues, createdAt]

    ScanListItem:
      type: object
      properties:
        id:
          type: string
        source:
          $ref: "#/components/schemas/ScanSource"
        processing_status:
          $ref: "#/components/schemas/ProcessingStatus"
        compliance_status:
          $ref: "#/components/schemas/ComplianceStatus"
        score:
          type: integer
          format: int32
          nullable: true
        createdAt:
          type: string
          format: date-time
      required: [id, source, processing_status, compliance_status, score, createdAt]

    ErrorResponse:
      type: object
      properties:
        error:
          type: boolean
          example: true
        message:
          type: string
      required: [error, message]

    HealthOkResponse:
      type: object
      properties:
        status:
          type: string
          enum: [ok]
      required: [status]

    HtmlPdfLink:
      type: object
      properties:
        url:
          type: string
          format: uri
        label:
          type: string
      required: [url, label]

    UrlAnalysisResult:
      type: object
      properties:
        type:
          type: string
          enum: [url_analysis]
        classification:
          type: string
          enum: [pdf, single_page, site_scan, protected]
        pdf_links:
          type: array
          items:
            $ref: "#/components/schemas/HtmlPdfLink"
        pdf_count:
          type: integer
          format: int32
        requires_auth:
          type: boolean
        domain:
          type: string
      required:
        [type, classification, pdf_links, pdf_count, requires_auth, domain]

    RemediationStatus:
      type: string
      description: Outcome of remediation (generation of an accessibility-enhanced document) for this request or batch item.
      enum: [completed, failed, unavailable]

    BatchMode:
      type: string
      enum: [audit, remediate]

    BatchStatus:
      type: string
      enum: [processing, completed, partial, failed]

    BatchItemStatus:
      type: string
      enum: [processing, completed, failed]

    BatchItem:
      type: object
      properties:
        url:
          type: string
        scan_id:
          type: string
          nullable: true
        status:
          $ref: "#/components/schemas/BatchItemStatus"
        score:
          type: integer
          format: int32
          nullable: true
        compliance_status:
          $ref: "#/components/schemas/ComplianceStatus"
        remediation_status:
          type: string
          nullable: true
          description: >
            In remediate mode, typically completed, failed, or unavailable; null in audit mode.
        remediation_summary:
          type: string
          nullable: true
          description: Human-readable remediation outcome when applicable.
        download_url:
          type: string
          nullable: true
          description: >
            Temporary link to download the accessibility-enhanced PDF produced by remediation, when available.
      required:
        [
          url,
          scan_id,
          status,
          score,
          compliance_status,
          remediation_status,
          remediation_summary,
          download_url,
        ]

    RemediateResponse:
      type: object
      properties:
        remediation_status:
          $ref: "#/components/schemas/RemediationStatus"
        remediation_summary:
          type: string
          description: Human-readable summary of remediation (accessibility-enhanced document generation) or why it did not complete.
        download_url:
          type: string
          format: uri
          nullable: true
          description: >
            Temporary link to download the accessibility-enhanced PDF generated by the system when remediation succeeds.
            Set API_PUBLIC_URL so clients receive a browser-reachable origin.
      required: [remediation_status, remediation_summary, download_url]

    ProcessUnifiedStatus:
      type: string
      enum: [completed, partial, failed]
      description: >
        completed: analysis succeeded and remediation produced a download_url for an accessibility-enhanced PDF.
        partial: analysis succeeded but remediation did not yield a downloadable document (or the remediation request failed).
        failed: analysis did not complete (remediation is skipped).

    ProcessAnalysisBlock:
      type: object
      nullable: true
      properties:
        score:
          type: integer
          format: int32
        compliance_status:
          $ref: "#/components/schemas/ComplianceStatus"
        issues:
          allOf:
            - $ref: "#/components/schemas/ScanIssuesBySeverity"
          nullable: true
        summary:
          type: string
      required: [score, compliance_status, issues, summary]

    ProcessRemediationBlock:
      type: object
      properties:
        status:
          type: string
          description: Processor remediation_status, failed if the API could not call the processor, or skipped when analysis failed.
        summary:
          type: string
        download_url:
          type: string
          format: uri
          nullable: true
          description: >
            Temporary link to download the accessibility-enhanced PDF generated by the system when present.
      required: [status, summary, download_url]

    ProcessResponse:
      type: object
      properties:
        status:
          $ref: "#/components/schemas/ProcessUnifiedStatus"
        analysis:
          allOf:
            - $ref: "#/components/schemas/ProcessAnalysisBlock"
          nullable: true
        remediation:
          $ref: "#/components/schemas/ProcessRemediationBlock"
      required: [status, analysis, remediation]

    Batch:
      type: object
      properties:
        batch_id:
          type: string
        mode:
          $ref: "#/components/schemas/BatchMode"
        status:
          $ref: "#/components/schemas/BatchStatus"
        total:
          type: integer
          format: int32
        processed:
          type: integer
          format: int32
        createdAt:
          type: string
          format: date-time
        items:
          type: array
          items:
            $ref: "#/components/schemas/BatchItem"
      required: [batch_id, mode, status, total, processed, createdAt, items]

    BatchCreateResponse:
      type: object
      properties:
        batch_id:
          type: string
        status:
          $ref: "#/components/schemas/BatchStatus"
      required: [batch_id, status]

paths:
  /health:
    get:
      summary: Liveness check
      description: No API key required. Intended for load balancers and orchestrators.
      security: []
      responses:
        "200":
          description: Service is alive
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/HealthOkResponse"

  /v1/status:
    get:
      summary: Service identification
      description: No API key required.
      security: []
      responses:
        "200":
          description: Service is healthy
          content:
            application/json:
              schema:
                type: object
                properties:
                  ok:
                    type: boolean
                  service:
                    type: string
                required: [ok, service]

  /v1/scans/upload:
    post:
      summary: Scan a PDF uploaded as a file
      security:
        - apiKey: []
      requestBody:
        required: true
        content:
          multipart/form-data:
            schema:
              type: object
              properties:
                file:
                  type: string
                  format: binary
              required: [file]
      responses:
        "200":
          description: Scan completed successfully
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ScanResult"
        "400":
          description: Invalid request (missing file, not a PDF, etc.)
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "401":
          description: Invalid or missing API key
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "413":
          description: PDF exceeds max size (default 25 MB; `MAX_PDF_BYTES`)
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "429":
          description: Rate limited
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "504":
          description: Processor analyze request timed out (`PROCESSOR_ANALYZE_TIMEOUT_MS`)
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "502":
          description: Processor error or scan could not be completed
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"

  /v1/scans/url:
    post:
      summary: Scan a PDF available at a public URL
      security:
        - apiKey: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                url:
                  type: string
                  format: uri
              required: [url]
      responses:
        "200":
          description: Direct PDF scan result or HTML page analysis for batch/UI flows
          content:
            application/json:
              schema:
                oneOf:
                  - $ref: "#/components/schemas/ScanResult"
                  - $ref: "#/components/schemas/UrlAnalysisResult"
        "400":
          description: Invalid URL or the URL did not return a PDF
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "401":
          description: Invalid or missing API key
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "413":
          description: Downloaded PDF exceeds max size (`MAX_PDF_BYTES`)
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "429":
          description: Rate limited
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "502":
          description: Scan did not complete (e.g. processor error)
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"

  /v1/scans/async:
    post:
      summary: Start an asynchronous scan from a file upload or URL
      description: >
        Starts a scan and returns immediately. Poll GET /v1/scans/{scanId} to
        retrieve the final result.
      security:
        - apiKey: []
      requestBody:
        required: true
        content:
          multipart/form-data:
            schema:
              type: object
              properties:
                file:
                  type: string
                  format: binary
          application/json:
            schema:
              type: object
              properties:
                url:
                  type: string
                  format: uri
      responses:
        "202":
          description: Scan accepted for processing
          content:
            application/json:
              schema:
                type: object
                properties:
                  scan_id:
                    type: string
                  processing_status:
                    $ref: "#/components/schemas/ProcessingStatus"
                required: [scan_id, processing_status]
        "400":
          description: Invalid request (missing file or URL, invalid URL, not a PDF)
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "401":
          description: Invalid or missing API key
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "413":
          description: PDF exceeds max size (`MAX_PDF_BYTES`)
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "429":
          description: Rate limited
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"

  /v1/process:
    post:
      summary: Single-step analysis + remediation (recommended)
      description: >
        Runs the same analysis as POST /v1/scans/upload, then remediation as POST /v1/remediate on the same PDF.
        Returns one JSON object with the analysis report and remediation outcome, plus an optional temporary
        download URL for an accessibility-enhanced PDF (not a compliance certificate).
        Does not create a scan record in GET /v1/scans. Requires a valid API key (same as scan routes).
      security:
        - apiKey: []
      requestBody:
        required: true
        content:
          multipart/form-data:
            schema:
              type: object
              properties:
                file:
                  type: string
                  format: binary
                  description: PDF file (use this or url field, not both).
                url:
                  type: string
                  format: uri
                  description: Direct PDF URL (multipart alternative to file).
                options:
                  type: string
                  description: Optional JSON string, e.g. {"forceOcr":true} to force the OCR rebuild path on the processor.
          application/json:
            schema:
              type: object
              properties:
                pdfBase64:
                  type: string
                  description: Base64-encoded PDF (use this or url, not both).
                url:
                  type: string
                  format: uri
                  description: Direct PDF URL.
                options:
                  type: object
                  properties:
                    forceOcr:
                      type: boolean
                      description: When true, processor uses the OCR rebuild path even for text-classified PDFs (requires Tesseract + Poppler).
      responses:
        "200":
          description: Unified result (completed, partial, or failed envelope)
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ProcessResponse"
        "400":
          description: Invalid request (missing input, not a PDF, URL not a direct PDF, etc.)
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "401":
          description: Invalid or missing API key
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "413":
          description: PDF exceeds max size
          content:
            application/json:
              schema:
                oneOf:
                  - $ref: "#/components/schemas/ErrorResponse"
                  - $ref: "#/components/schemas/ProcessResponse"
        "429":
          description: Rate limited
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "502":
          description: Analyze or remediate processor error (body may be ProcessResponse or ErrorResponse)
          content:
            application/json:
              schema:
                oneOf:
                  - $ref: "#/components/schemas/ErrorResponse"
                  - $ref: "#/components/schemas/ProcessResponse"
        "504":
          description: Processor timeout
          content:
            application/json:
              schema:
                oneOf:
                  - $ref: "#/components/schemas/ErrorResponse"
                  - $ref: "#/components/schemas/ProcessResponse"

  /v1/remediate:
    post:
      summary: Remediation — accessibility-enhanced PDF (OCR for image-heavy docs; metadata pass for text-based)
      description: >
        Generates an accessibility-enhanced PDF when possible: text-based inputs are processed for better metadata;
        image-heavy inputs use OCR where dependencies are installed. Does not certify ADA/WCAG compliance or full PDF/UA structure.
      security:
        - apiKey: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                pdfBase64:
                  type: string
                url:
                  type: string
                  format: uri
      responses:
        "200":
          description: Remediation result (analysis is not included; use POST /v1/process for combined analysis + remediation).
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/RemediateResponse"
        "400":
          description: Invalid request
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "401":
          description: Invalid or missing API key (unless server disables key for this route)
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "413":
          description: PDF exceeds max size (`MAX_PDF_BYTES`)
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "429":
          description: Rate limited
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "504":
          description: Processor remediate timed out (`PROCESSOR_REMEDIATE_TIMEOUT_MS`)
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "502":
          description: Processor unavailable
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"

  /v1/remediate/download/{token}:
    get:
      summary: Download a temporarily stored accessibility-enhanced PDF (token + TTL; no API key for browser compatibility)
      parameters:
        - name: token
          in: path
          required: true
          schema:
            type: string
      responses:
        "200":
          description: Accessibility-enhanced PDF binary (processed for better accessibility; not a compliance certificate).
          content:
            application/pdf:
              schema:
                type: string
                format: binary
        "404":
          description: Expired or unknown token
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"

  /v1/batch:
    post:
      summary: Start batch analysis or analysis+remediation for up to 20 direct PDF URLs
      description: >
        `audit` returns analysis per URL; `remediate` adds remediation when possible.
        Download URLs point to accessibility-enhanced PDFs, not certified compliance documents.
      security:
        - apiKey: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                mode:
                  $ref: "#/components/schemas/BatchMode"
                urls:
                  type: array
                  maxItems: 20
                  items:
                    type: string
              required: [mode, urls]
      responses:
        "200":
          description: Batch accepted for processing
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/BatchCreateResponse"
        "400":
          description: Invalid request (validation, max URL limit, etc.)
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "401":
          description: Invalid or missing API key (unless server disables key for this route)
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "429":
          description: Rate limited
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"

  /v1/batch/{batch_id}:
    get:
      summary: Get the full state of a batch job (analysis and optional remediation per item)
      security:
        - apiKey: []
      parameters:
        - name: batch_id
          in: path
          required: true
          schema:
            type: string
      responses:
        "200":
          description: Batch found
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Batch"
        "401":
          description: Invalid or missing API key (unless server disables key for this route)
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "404":
          description: Batch not found
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"

  /v1/scans/{scanId}:
    get:
      summary: Get a single scan by id
      security:
        - apiKey: []
      parameters:
        - name: scanId
          in: path
          required: true
          schema:
            type: string
      responses:
        "200":
          description: Scan found
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ScanResult"
        "401":
          description: Invalid or missing API key
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "404":
          description: Scan not found
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"

  /v1/scans:
    get:
      summary: List recent scans for the current API key
      security:
        - apiKey: []
      responses:
        "200":
          description: List of scans
          content:
            application/json:
              schema:
                type: object
                properties:
                  scans:
                    type: array
                    items:
                      $ref: "#/components/schemas/ScanListItem"
        "401":
          description: Invalid or missing API key
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "429":
          description: Rate limited
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"